321
doc/graphs.md
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
# Drawing Graphs
|
||||||
|
|
||||||
|
## Feature Set
|
||||||
|
|
||||||
|
Icinga Web comes with an SVG based graphing library that supports the basic graph types required for displaying monitoring
|
||||||
|
data. These include:
|
||||||
|
|
||||||
|
* **Pie Charts**, which display a set of data in a typical pie diagram.
|
||||||
|
* **Stacked Pie Charts**, which render one or multiple pies nested in anothe pie chart
|
||||||
|
* **Line Charts**, which display a set of datapoints as a line graph
|
||||||
|
* **Stacked Line Charts**, which display multiple line charts on top of each other, providing a cumulative view over
|
||||||
|
a set of datapoints
|
||||||
|
* **Bar Charts**, which display a set of datapoints as bars
|
||||||
|
* **Stacked Bar Charts**, which, like the Stacked Line Chart, combines several charts and displays them on top of each other
|
||||||
|
|
||||||
|
## Creating Grid Charts (Line and Bar Charts)
|
||||||
|
|
||||||
|
### Base Api Synopsis
|
||||||
|
|
||||||
|
The `Icinga/Chart/GridChart` class provides the calls required for setting up Grid Charts. A GridChart draws three
|
||||||
|
seperate parts: Axis, Legend and the Gridarea.
|
||||||
|
|
||||||
|
To create a new Grid, simply create a `GridChart` object (the constructor takes no parameters):
|
||||||
|
|
||||||
|
**Example #1: Create a grid chart**
|
||||||
|
|
||||||
|
$this->chart = new GridChart();
|
||||||
|
|
||||||
|
Now you can go on and customize the chart to fit your needs (this will be explained in depth in the next sections).
|
||||||
|
|
||||||
|
**Example #2: Customize the grid chart**
|
||||||
|
|
||||||
|
$this->chart
|
||||||
|
->setAxisMin(null, 0) // Set the Y-axis to always start at 0
|
||||||
|
->setAxisMax(null, 100) // Set the Y-Axis to end at 100
|
||||||
|
->setAxisLabel("X axis label", "Y axis label"); // Set labels for X-axis and Y-axis
|
||||||
|
|
||||||
|
And finally you can draw data:
|
||||||
|
|
||||||
|
**Example #3: Drawing graphs**
|
||||||
|
|
||||||
|
$this->chart->drawLines(
|
||||||
|
array(
|
||||||
|
'label' => 'A Graph Line',
|
||||||
|
'color' => 'red',
|
||||||
|
'width' => '5',
|
||||||
|
'data' => array(array(0, 10), array(2, 40), array(3, 55), array(7, 92))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
This example would produce a graph like this if rendered:
|
||||||
|
|
||||||
|
![Simple Line Graph][graph1]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Graph Setup Methods
|
||||||
|
|
||||||
|
When creating the above graph without any setup options (like `setAxisMin`), it would use default values when being rendered.
|
||||||
|
This means:
|
||||||
|
|
||||||
|
* No label for X-Axis and Y-Axis
|
||||||
|
* The X/Y axis minimal value is the lowest X/Y value from the dataset
|
||||||
|
* The X/Y axis maximum value is the highest X/Y value from the dataset
|
||||||
|
|
||||||
|
Let's create a minimal example for this:
|
||||||
|
|
||||||
|
**Example #4: The most simple line graph**
|
||||||
|
|
||||||
|
$this->chart = new GridChart();
|
||||||
|
$this->chart->drawLines(
|
||||||
|
array(
|
||||||
|
'data' => array(array(0, 10), array(2, 40), array(3, 55), array(7, 92))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
![The Most Simple Line Graph][graph2]
|
||||||
|
|
||||||
|
|
||||||
|
#### Adding Axis Labels
|
||||||
|
|
||||||
|
A graph without axis labels is rather useless. With the `GridChart::setAxisLabel($xAxisLabel, $yAxisLabel)` method you
|
||||||
|
can define the axis labels for both the X and Y axis:
|
||||||
|
|
||||||
|
**Example #5: Adding axis labels**
|
||||||
|
|
||||||
|
$this->chart = new GridChart();
|
||||||
|
$this->chart->setAxisLabel("X axis label", "Y axis label");
|
||||||
|
$this->chart->drawLines(
|
||||||
|
array(
|
||||||
|
'data' => array(array(0, 10), array(2, 40), array(3, 55), array(7, 92))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
![Line Graph With Label][graph3]
|
||||||
|
|
||||||
|
#### Defining Axis Types
|
||||||
|
|
||||||
|
Normally, axis display their values as numeric, linear types. You can overwrite the axis for the X or Y direction with
|
||||||
|
one that suits your needs more specifically. Supported axis are:
|
||||||
|
|
||||||
|
* Linear Axis: This is the default axis that displays numeric values with an equal distance between each tick
|
||||||
|
|
||||||
|
**Example #6: Defining A Linear Axis With A Custom Number Of Ticks**
|
||||||
|
|
||||||
|
$this->chart = new GridChart();
|
||||||
|
$this->chart->setAxisLabel("X axis label", "Y axis label");
|
||||||
|
$this->chart->setXAxis(Axis::linearUnit(40));
|
||||||
|
$this->chart->setYAxis(Axis::linearUnit(10));
|
||||||
|
$this->chart->drawLines(
|
||||||
|
array(
|
||||||
|
'data' => array(array(0, 10), array(2, 40), array(3, 55), array(7, 92))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
![Line Graph With Custom Tick Count][graph4]
|
||||||
|
|
||||||
|
|
||||||
|
* Calendar Axis: The calendar axis is a special axis for using timestamps in the axis. It will display the ticks as
|
||||||
|
sensible time values
|
||||||
|
|
||||||
|
**Example #7: Defining A Calendar Axis**
|
||||||
|
|
||||||
|
$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))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
![Line Graph With Custom Tick Count][graph5]
|
||||||
|
|
||||||
|
## Line Charts
|
||||||
|
|
||||||
|
We've already seen an example of line charts in the last section, but this was rather minimal. The call for creating
|
||||||
|
Line Charts in the Chart Api is `GridChart::drawLines(array $lineDefinition1, array $lineDefinition2, ...)`, while '...'
|
||||||
|
means 'as many definitions as you want'.
|
||||||
|
|
||||||
|
$lineDefinition is an configuration array that describes how your data will be displayed. Possible configuration options
|
||||||
|
are:
|
||||||
|
|
||||||
|
* **label** The text that will be displayed in the legend of the graph for this line. If none is given simply
|
||||||
|
'Dataset %nr%' will be displayed, with %nr% meaning a number starting at 1 and incrementing for every
|
||||||
|
line without a label
|
||||||
|
* **stack** If provided, this graph will be shown on top of each other graph in the same stack and causes all
|
||||||
|
graphs in the same stack to be rendered culmulative
|
||||||
|
* **discrete** Set to display the line in a discrete manner, i.e. using hard steps between values instead of drawing
|
||||||
|
a interpolated line between points
|
||||||
|
* **color** The color to use for the line or fill, either in Hex form or as a string supported in the SVG style tag
|
||||||
|
* **palette** (Ignored if 'color' is set) The color palette to use for determining the line or fill color
|
||||||
|
* **fill** True to fill the graph instead of drawing a line. Take care of the graph ordering when using this
|
||||||
|
option, as previously drawn graphs will be hidden if they overlap this graph.
|
||||||
|
* **showPoints** Set true to emphasize datapoints with additional dots
|
||||||
|
* **width** Set the thickness of the line stroke in px (default: 5)
|
||||||
|
* **data** The dataset as an two dimensional array in the form `array(array($x1, $y2), array($x2, $y2), ...)
|
||||||
|
|
||||||
|
**Example #8: Various Line Graph Options**
|
||||||
|
|
||||||
|
|
||||||
|
$this->chart->drawLines(
|
||||||
|
array(
|
||||||
|
'label' => 'Hosts critical',
|
||||||
|
'palette' => Palette::PROBLEM,
|
||||||
|
'stack' => 'stack1',
|
||||||
|
'fill' => true,
|
||||||
|
'data' => $data2
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'label' => 'Hosts warning',
|
||||||
|
'stack' => 'stack1',
|
||||||
|
'palette' => Palette::WARNING,
|
||||||
|
'fill' => true,
|
||||||
|
'showPoints' => true,
|
||||||
|
'data' => $data
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'label' => 'Hosts ok',
|
||||||
|
'discrete' => true,
|
||||||
|
'color' => '#00ff00',
|
||||||
|
'fill' => false,
|
||||||
|
'showPoints' => true,
|
||||||
|
'width' => '10',
|
||||||
|
'data' => $data3
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
You can see the effects here, notice how the first two lines are stacked:
|
||||||
|
|
||||||
|
![Various Line Graph Options][graph6]
|
||||||
|
|
||||||
|
|
||||||
|
## Bar Charts
|
||||||
|
|
||||||
|
Bar Charts almost offer the same functionality as Line Charts, but some configuration options from Line Charts don't make sense
|
||||||
|
and are therefore omitted.
|
||||||
|
The call for creating Line Charts in the Chart Api is `GridChart::drawBars(array $lineDefinition1, array $lineDefinition2, ...)`,
|
||||||
|
while '...' means 'as many definitions as you want'. Possible configuration options are:
|
||||||
|
|
||||||
|
* **label** The text that will be displayed in the legend of the graph for this line. If none is given simply
|
||||||
|
'Dataset %nr%' will be displayed, with %nr% meaning a number starting at 1 and incrementing for every
|
||||||
|
line without a label
|
||||||
|
* **stack** If provided, this graph will be shown on top of each other graph in the same stack and causes all
|
||||||
|
graphs in the same stack to be rendered culmulative
|
||||||
|
* **color** The color to use for filling the bar, either in Hex form or as a string supported in the SVG style tag
|
||||||
|
* **palette** (Ignored if 'color' is set) The color palette to use for determining the fill color
|
||||||
|
* **width** Set the thickness of the line stroke in px (default: 1)
|
||||||
|
* **data** The dataset as an two dimensional array in the form `array(array($x1, $y2), array($x2, $y2), ...)
|
||||||
|
|
||||||
|
The same graph as rendered above would look as follows when using `drawBars` instead of `drawLines`. If you don't want
|
||||||
|
the labels to show you can use the 'disableLegend()' call on the GridChart object.
|
||||||
|
|
||||||
|
**Example #9: Various Bar Chart Options**
|
||||||
|
|
||||||
|
$this->chart->drawBars(
|
||||||
|
array(
|
||||||
|
'label' => 'Hosts critical',
|
||||||
|
'palette' => Palette::PROBLEM,
|
||||||
|
'stack' => 'stack1',
|
||||||
|
'data' => $data2
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'label' => 'Hosts warning',
|
||||||
|
'stack' => 'stack1',
|
||||||
|
'palette' => Palette::WARNING,
|
||||||
|
'data' => $data
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'label' => 'Hosts ok',
|
||||||
|
'color' => '#00ff00',
|
||||||
|
'width' => '10',
|
||||||
|
'data' => $data3
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
![Various Line Graph Options][graph7]
|
||||||
|
|
||||||
|
## Pie Charts
|
||||||
|
|
||||||
|
### The PieChart Object
|
||||||
|
|
||||||
|
Additionally to Line and Bar Charts, the Graphing Api also supports drawing Pie charts. In order to work with Pie charts
|
||||||
|
you have to create an `Icinga\Chart\PieChart` object first:
|
||||||
|
|
||||||
|
**Example #10: Creating a PieChart Object**
|
||||||
|
|
||||||
|
$pie = new PieChart();
|
||||||
|
|
||||||
|
### Drawing Pies
|
||||||
|
|
||||||
|
Pies are now drawn using the `PieChart::drawPies(array $pieDefinition1, array $pieDefinition2, ...)` method:
|
||||||
|
|
||||||
|
**Example #11: Example PieChart Definition**
|
||||||
|
|
||||||
|
$pie->drawPie(array(
|
||||||
|
'data' => array(5,80,1),
|
||||||
|
'palette' => array(Palette::PROBLEM, Palette::OK, Palette::WARNING),
|
||||||
|
'labels' => array(
|
||||||
|
'Hosts down', 'Hosts up', 'Hosts unknown'
|
||||||
|
)
|
||||||
|
));
|
||||||
|
|
||||||
|
This would produce a Pie Chart similar to this:
|
||||||
|
|
||||||
|
![Example Pie Chart][graph8]
|
||||||
|
|
||||||
|
Notice how every datapoint has it's own label and palette definition. Possible attributes for $pieDefinition are:
|
||||||
|
|
||||||
|
* **labels**: An array containing a label for every definition in the 'data' array
|
||||||
|
* **colors**: An array of colors to use for every definition in the 'data' array
|
||||||
|
* **palette**: (ignored when using 'colors') An array containing the palette to user for every definition in the 'data'
|
||||||
|
array
|
||||||
|
* **data** An array containing of numeric values that define the relative sizes of the pie slices to the whole pie
|
||||||
|
|
||||||
|
If you don't want the labels to show you can use the 'disableLegend()' call on the PieChart object.
|
||||||
|
|
||||||
|
### Stacked Pies
|
||||||
|
|
||||||
|
When adding multiple pies, they will be per default shown as a stacked pie:
|
||||||
|
|
||||||
|
**Example #12: Stacked Pie Charts**
|
||||||
|
|
||||||
|
$pie = new PieChart();
|
||||||
|
$pie->drawPie(array(
|
||||||
|
'data' => array(5,80,1),
|
||||||
|
'palette' => array(Palette::PROBLEM, Palette::OK, Palette::WARNING),
|
||||||
|
'labels' => array(
|
||||||
|
'Hosts down', 'Hosts up', 'Hosts unknown'
|
||||||
|
)
|
||||||
|
), array(
|
||||||
|
'data' => array(40,60,90,2),
|
||||||
|
'palette' => array(Palette::PROBLEM, Palette::WARNING, Palette::OK, Palette::NEUTRAL),
|
||||||
|
'labels' => array('Services down', 'Services Warning', 'Services OK', 'Services pending')
|
||||||
|
));
|
||||||
|
|
||||||
|
![Example Pie Chart][graph9]
|
||||||
|
|
||||||
|
## Rendering in templates:
|
||||||
|
|
||||||
|
Rendering is straightforward, assuming $svg is the PieChart/GridChart object, you just call render() to create an SVG:
|
||||||
|
|
||||||
|
myTemplate.phtml
|
||||||
|
|
||||||
|
<div style="border:1px dashed black;width:800px;height:400px">
|
||||||
|
<?=
|
||||||
|
$svg->render();
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
[graph1]: res/GraphExample#1.png
|
||||||
|
[graph2]: res/GraphExample#2.png
|
||||||
|
[graph3]: res/GraphExample#3.png
|
||||||
|
[graph4]: res/GraphExample#4.png
|
||||||
|
[graph5]: res/GraphExample#5.png
|
||||||
|
[graph6]: res/GraphExample#6.png
|
||||||
|
[graph7]: res/GraphExample#7.png
|
||||||
|
[graph8]: res/GraphExample#8.png
|
||||||
|
[graph9]: res/GraphExample#9.png
|
BIN
doc/res/GraphExample#1.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
doc/res/GraphExample#2.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
doc/res/GraphExample#3.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
doc/res/GraphExample#4.png
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
doc/res/GraphExample#5.png
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
doc/res/GraphExample#6.png
Normal file
After Width: | Height: | Size: 58 KiB |
BIN
doc/res/GraphExample#7.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
doc/res/GraphExample#8.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
doc/res/GraphExample#9.png
Normal file
After Width: | Height: | Size: 37 KiB |
@ -8,100 +8,178 @@ use Icinga\Chart\Primitive\Drawable;
|
|||||||
use Icinga\Chart\Primitive\Line;
|
use Icinga\Chart\Primitive\Line;
|
||||||
use Icinga\Chart\Primitive\Text;
|
use Icinga\Chart\Primitive\Text;
|
||||||
use Icinga\Chart\Render\RenderContext;
|
use Icinga\Chart\Render\RenderContext;
|
||||||
|
|
||||||
use Icinga\Chart\Unit\AxisUnit;
|
use Icinga\Chart\Unit\AxisUnit;
|
||||||
use Icinga\Chart\Unit\LinearAxis;
|
use Icinga\Chart\Unit\CalendarUnit;
|
||||||
|
use Icinga\Chart\Unit\LinearUnit;
|
||||||
|
|
||||||
|
use DOMElement;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Axis for both line and bar chart. It defines the unit, scale, label and ticks.
|
* Axis class for the GridChart class.
|
||||||
|
*
|
||||||
|
* Implements drawing functions for the axis and it's labels but delegates tick and label calculations
|
||||||
|
* to the AxisUnit implementations
|
||||||
|
*
|
||||||
|
* @see GridChart
|
||||||
|
* @see AxisUnit
|
||||||
*/
|
*/
|
||||||
class Axis implements Drawable
|
class Axis implements Drawable
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Whether to draw the horizontal lines for the background grid
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
private $drawXGrid = true;
|
private $drawXGrid = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to draw the vertical lines for the background grid
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
private $drawYGrid = true;
|
private $drawYGrid = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The label for the x axis
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
private $xLabel = "";
|
private $xLabel = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The label for the y axis
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
private $yLabel = "";
|
private $yLabel = "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* The AxisUnit implementation to use for calculating the ticks for the x axis
|
||||||
|
*
|
||||||
* @var AxisUnit
|
* @var AxisUnit
|
||||||
*/
|
*/
|
||||||
private $xUnit = null;
|
private $xUnit = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* The AxisUnit implementation to use for calculating the ticks for the y axis
|
||||||
|
*
|
||||||
* @var AxisUnit
|
* @var AxisUnit
|
||||||
*/
|
*/
|
||||||
private $yUnit = null;
|
private $yUnit = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inform the axis about an added dataset
|
||||||
|
*
|
||||||
|
* This is especially needed when one or more AxisUnit implementations dynamically define
|
||||||
|
* their min or max values, as this is the point where they detect the min and max value
|
||||||
|
* from the datasets
|
||||||
|
*
|
||||||
|
* @param array $dataset An dataset to respect on axis generation
|
||||||
|
*/
|
||||||
public function addDataset(array $dataset)
|
public function addDataset(array $dataset)
|
||||||
{
|
{
|
||||||
$this->xUnit->addValues($dataset, 0);
|
$this->xUnit->addValues($dataset, 0);
|
||||||
$this->yUnit->addValues($dataset, 1);
|
$this->yUnit->addValues($dataset, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the AxisUnit implementation to use for generating the x axis
|
||||||
|
*
|
||||||
|
* @param AxisUnit $unit The AxisUnit implementation to use for the x axis
|
||||||
|
*
|
||||||
|
* @return self This Axis Object
|
||||||
|
* @see Axis::CalendarUnit
|
||||||
|
* @see Axis::LinearUnit
|
||||||
|
*/
|
||||||
public function setUnitForXAxis(AxisUnit $unit)
|
public function setUnitForXAxis(AxisUnit $unit)
|
||||||
{
|
{
|
||||||
$this->xUnit = $unit;
|
$this->xUnit = $unit;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the AxisUnit implementation to use for generating the y axis
|
||||||
|
*
|
||||||
|
* @param AxisUnit $unit The AxisUnit implementation to use for the y axis
|
||||||
|
*
|
||||||
|
* @return self This Axis Object
|
||||||
|
* @see Axis::CalendarUnit
|
||||||
|
* @see Axis::LinearUnit
|
||||||
|
*/
|
||||||
public function setUnitForYAxis(AxisUnit $unit)
|
public function setUnitForYAxis(AxisUnit $unit)
|
||||||
{
|
{
|
||||||
$this->yUnit = $unit;
|
$this->yUnit = $unit;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getApproximateLabelSize()
|
/**
|
||||||
|
* Return the padding this axis requires
|
||||||
|
*
|
||||||
|
* @return array An array containing the padding for all sides
|
||||||
|
*/
|
||||||
|
public function getRequiredPadding()
|
||||||
{
|
{
|
||||||
return strlen($this->getMax());
|
return array(10, 5, 15, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toSvg(RenderContext $ctx)
|
/**
|
||||||
{
|
* Render the horizontal axis
|
||||||
$group = $ctx->getDocument()->createElement('g');
|
*
|
||||||
$this->renderHorizontal($ctx, $group);
|
* @param RenderContext $ctx The context to use for rendering
|
||||||
$this->renderVertical($ctx, $group);
|
* @param DOMElement $group The DOMElement this axis will be added to
|
||||||
return $group;
|
*/
|
||||||
}
|
private function renderHorizontalAxis(RenderContext $ctx, DOMElement $group)
|
||||||
|
|
||||||
public function getRequiredPadding() {
|
|
||||||
return array(5, 10, 5, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function renderHorizontal(RenderContext $ctx, \DOMElement $group)
|
|
||||||
{
|
{
|
||||||
$line = new Line(0, 100, 100, 100);
|
$line = new Line(0, 100, 100, 100);
|
||||||
$line->setStrokeWidth(2);
|
$line->setStrokeWidth(2);
|
||||||
$group->appendChild($line->toSvg($ctx));
|
$group->appendChild($line->toSvg($ctx));
|
||||||
|
|
||||||
|
// contains the approximate end position of the last label
|
||||||
|
$lastLabelEnd = -1;
|
||||||
|
$shift = 0;
|
||||||
|
|
||||||
foreach ($this->xUnit as $label => $pos) {
|
foreach ($this->xUnit as $label => $pos) {
|
||||||
|
// If the last label would overlap this label we shift the y axis a bit
|
||||||
|
if ($lastLabelEnd > $pos) {
|
||||||
|
$shift = ($shift + 5) % 10;
|
||||||
|
} else {
|
||||||
|
$shift = 0;
|
||||||
|
}
|
||||||
|
|
||||||
$tick = new Line($pos, 100, $pos, 102);
|
$tick = new Line($pos, 100, $pos, 102);
|
||||||
$group->appendChild($tick->toSvg($ctx));
|
$group->appendChild($tick->toSvg($ctx));
|
||||||
|
|
||||||
$labelField = new Text($pos+0.5, 105, $label);
|
$labelField = new Text($pos + 0.5, 105 + $shift, $label);
|
||||||
$labelField->setAlignment(Text::ALIGN_MIDDLE)
|
$labelField->setAlignment(Text::ALIGN_MIDDLE)
|
||||||
->setFontSize('2em');
|
->setFontSize('1.8em');
|
||||||
|
|
||||||
$group->appendChild($labelField->toSvg($ctx));
|
$group->appendChild($labelField->toSvg($ctx));
|
||||||
|
|
||||||
if ($this->drawYGrid) {
|
if ($this->drawYGrid) {
|
||||||
$bgLine = new Line($pos, 0, $pos, 102);
|
$bgLine = new Line($pos, 0, $pos, 100);
|
||||||
$bgLine->setStrokeWidth(0.5)
|
$bgLine->setStrokeWidth(0.5)
|
||||||
->setStrokeColor('#232');
|
->setStrokeColor('#232');
|
||||||
$group->appendChild($bgLine->toSvg($ctx));
|
$group->appendChild($bgLine->toSvg($ctx));
|
||||||
}
|
}
|
||||||
|
$lastLabelEnd = $pos + strlen($label);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// render the label for this axis
|
||||||
if ($this->xLabel) {
|
if ($this->xLabel) {
|
||||||
$label = new Text(50, 110, $this->xLabel);
|
$axisLabel = new Text(50, 115, $this->xLabel);
|
||||||
$label->setFontSize('1.7em')
|
$axisLabel->setFontSize('2em')
|
||||||
->setAlignment(Text::ALIGN_MIDDLE);
|
->setAlignment(Text::ALIGN_MIDDLE);
|
||||||
$group->appendChild($label->toSvg($ctx));
|
$group->appendChild($axisLabel->toSvg($ctx));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
}
|
* Render the vertical axis
|
||||||
|
*
|
||||||
private function renderVertical(RenderContext $ctx, \DOMElement $group)
|
* @param RenderContext $ctx The context to use for rendering
|
||||||
|
* @param DOMElement $group The DOMElement this axis will be added to
|
||||||
|
*/
|
||||||
|
private function renderVerticalAxis(RenderContext $ctx, DOMElement $group)
|
||||||
{
|
{
|
||||||
$line = new Line(0, 0, 0, 100);
|
$line = new Line(0, 0, 0, 100);
|
||||||
$line->setStrokeWidth(2);
|
$line->setStrokeWidth(2);
|
||||||
@ -113,7 +191,7 @@ class Axis implements Drawable
|
|||||||
$group->appendChild($tick->toSvg($ctx));
|
$group->appendChild($tick->toSvg($ctx));
|
||||||
|
|
||||||
$labelField = new Text(-0.5, $pos+0.5, $label);
|
$labelField = new Text(-0.5, $pos+0.5, $label);
|
||||||
$labelField->setFontSize('2em')
|
$labelField->setFontSize('1.8em')
|
||||||
->setAlignment(Text::ALIGN_END);
|
->setAlignment(Text::ALIGN_END);
|
||||||
|
|
||||||
$group->appendChild($labelField->toSvg($ctx));
|
$group->appendChild($labelField->toSvg($ctx));
|
||||||
@ -122,65 +200,130 @@ class Axis implements Drawable
|
|||||||
$bgLine->setStrokeWidth(0.5)
|
$bgLine->setStrokeWidth(0.5)
|
||||||
->setStrokeColor('#343');
|
->setStrokeColor('#343');
|
||||||
$group->appendChild($bgLine->toSvg($ctx));
|
$group->appendChild($bgLine->toSvg($ctx));
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->yLabel) {
|
if ($this->yLabel) {
|
||||||
$label = new Text(-5, 50, $this->yLabel);
|
$axisLabel = new Text(-10, 50, $this->yLabel);
|
||||||
$label->setFontSize('1.7em')
|
$axisLabel->setFontSize('2em')
|
||||||
->setAdditionalStyle(Text::ORIENTATION_VERTICAL)
|
->setAdditionalStyle(Text::ORIENTATION_VERTICAL)
|
||||||
->setAlignment(Text::ALIGN_MIDDLE);
|
->setAlignment(Text::ALIGN_MIDDLE);
|
||||||
|
|
||||||
$group->appendChild($label->toSvg($ctx));
|
$group->appendChild($axisLabel->toSvg($ctx));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory method, create an Axis instance using Linear ticks as the unit
|
||||||
|
*
|
||||||
|
* @return Axis The axis that has been created
|
||||||
|
* @see LinearUnit
|
||||||
|
*/
|
||||||
public static function createLinearAxis()
|
public static function createLinearAxis()
|
||||||
{
|
{
|
||||||
$axis = new Axis();
|
$axis = new Axis();
|
||||||
$axis->setUnitForXAxis(new LinearAxis());
|
$axis->setUnitForXAxis(self::linearUnit());
|
||||||
$axis->setUnitForYAxis(new LinearAxis());
|
$axis->setUnitForYAxis(self::linearUnit());
|
||||||
return $axis;
|
return $axis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the label for the x axis.
|
||||||
|
*
|
||||||
|
* An empty string means 'no label'
|
||||||
|
*
|
||||||
|
* @param string $label The label to use for the x axis
|
||||||
|
*
|
||||||
|
* @return $this Fluid interface
|
||||||
|
*/
|
||||||
public function setXLabel($label)
|
public function setXLabel($label)
|
||||||
{
|
{
|
||||||
$this->xLabel = $label;
|
$this->xLabel = $label;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the label for the y axis.
|
||||||
|
*
|
||||||
|
* An empty string means 'no label'
|
||||||
|
*
|
||||||
|
* @param string $label The label to use for the y axis
|
||||||
|
*
|
||||||
|
* @return $this Fluid interface
|
||||||
|
*/
|
||||||
public function setYLabel($label)
|
public function setYLabel($label)
|
||||||
{
|
{
|
||||||
$this->yLabel = $label;
|
$this->yLabel = $label;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the labels minimum value for the x axis
|
||||||
|
*
|
||||||
|
* Setting the value to null let's the axis unit decide which value to use for the minimum
|
||||||
|
*
|
||||||
|
* @param mixed $label The minimum value to use for the x axis
|
||||||
|
*
|
||||||
|
* @return $this Fluid interface
|
||||||
|
*/
|
||||||
public function setXMin($xMin)
|
public function setXMin($xMin)
|
||||||
{
|
{
|
||||||
$this->xUnit->setMin($xMin);
|
$this->xUnit->setMin($xMin);
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the labels minimum value for the y axis
|
||||||
|
*
|
||||||
|
* Setting the value to null let's the axis unit decide which value to use for the minimum
|
||||||
|
*
|
||||||
|
* @param mixed $label The minimum value to use for the x axis
|
||||||
|
*
|
||||||
|
* @return $this Fluid interface
|
||||||
|
*/
|
||||||
public function setYMin($yMin)
|
public function setYMin($yMin)
|
||||||
{
|
{
|
||||||
$this->yUnit->setMin($yMin);
|
$this->yUnit->setMin($yMin);
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the labels maximum value for the x axis
|
||||||
|
*
|
||||||
|
* Setting the value to null let's the axis unit decide which value to use for the maximum
|
||||||
|
*
|
||||||
|
* @param mixed $label The minimum value to use for the x axis
|
||||||
|
*
|
||||||
|
* @return $this Fluid interface
|
||||||
|
*/
|
||||||
public function setXMax($xMax)
|
public function setXMax($xMax)
|
||||||
{
|
{
|
||||||
$this->xUnit->setMax($xMax);
|
$this->xUnit->setMax($xMax);
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the labels maximum value for the y axis
|
||||||
|
*
|
||||||
|
* Setting the value to null let's the axis unit decide which value to use for the maximum
|
||||||
|
*
|
||||||
|
* @param mixed $label The minimum value to use for the y axis
|
||||||
|
*
|
||||||
|
* @return $this Fluid interface
|
||||||
|
*/
|
||||||
public function setYMax($yMax)
|
public function setYMax($yMax)
|
||||||
{
|
{
|
||||||
$this->yUnit->setMax($yMax);
|
$this->yUnit->setMax($yMax);
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform all coordinates of the given dataset to coordinates that fit the graph's coordinate system
|
||||||
|
*
|
||||||
|
* @param array $dataSet The absolute coordinates as provided in the draw call
|
||||||
|
*
|
||||||
|
* @return array A graph relative representation of the given coordinates
|
||||||
|
*/
|
||||||
public function transform(array &$dataSet)
|
public function transform(array &$dataSet)
|
||||||
{
|
{
|
||||||
$result = array();
|
$result = array();
|
||||||
@ -190,9 +333,45 @@ class Axis implements Drawable
|
|||||||
100 - $this->yUnit->transform($points[1])
|
100 - $this->yUnit->transform($points[1])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an AxisUnit that can be used in the axis to represent timestamps
|
||||||
|
*
|
||||||
|
* @return CalendarUnit
|
||||||
|
*/
|
||||||
|
public static function calendarUnit()
|
||||||
|
{
|
||||||
|
return new CalendarUnit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an AxisUnit that can be used in the axis to represent a dataset as equally distributed
|
||||||
|
* ticks
|
||||||
|
*
|
||||||
|
* @param int $ticks
|
||||||
|
* @return LinearUnit
|
||||||
|
*/
|
||||||
|
public static function linearUnit($ticks = 10)
|
||||||
|
{
|
||||||
|
return new LinearUnit($ticks);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the SVG representation of this object
|
||||||
|
*
|
||||||
|
* @param RenderContext $ctx The context to use for calculations
|
||||||
|
*
|
||||||
|
* @return DOMElement
|
||||||
|
*
|
||||||
|
* @see Drawable::toSvg
|
||||||
|
*/
|
||||||
|
public function toSvg(RenderContext $ctx)
|
||||||
|
{
|
||||||
|
$group = $ctx->getDocument()->createElement('g');
|
||||||
|
$this->renderHorizontalAxis($ctx, $group);
|
||||||
|
$this->renderVerticalAxis($ctx, $group);
|
||||||
|
return $group;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -29,30 +29,19 @@
|
|||||||
namespace Icinga\Chart;
|
namespace Icinga\Chart;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use Iterator;
|
|
||||||
use Icinga\Chart\Legend;
|
use Icinga\Chart\Legend;
|
||||||
use Icinga\Chart\Palette;
|
use Icinga\Chart\Palette;
|
||||||
use Icinga\Chart\Primitive\Drawable;
|
use Icinga\Chart\Primitive\Drawable;
|
||||||
use Icinga\Chart\SVGRenderer;
|
use Icinga\Chart\SVGRenderer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for charts
|
* Base class for charts, extended by all other Chart classes.
|
||||||
*/
|
*/
|
||||||
abstract class Chart implements Drawable
|
abstract class Chart implements Drawable
|
||||||
{
|
{
|
||||||
private $xAxis;
|
|
||||||
|
|
||||||
private $yAxis;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Series to plot
|
* SVG renderer that handles
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $dataSets = array();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SVG renderer
|
|
||||||
*
|
*
|
||||||
* @var SVGRenderer
|
* @var SVGRenderer
|
||||||
*/
|
*/
|
||||||
@ -63,64 +52,54 @@ abstract class Chart implements Drawable
|
|||||||
*
|
*
|
||||||
* @var Legend
|
* @var Legend
|
||||||
*/
|
*/
|
||||||
public $legend;
|
protected $legend;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The style-palette for this chart
|
* The style-palette for this chart
|
||||||
*
|
*
|
||||||
* @var Palette
|
* @var Palette
|
||||||
*/
|
*/
|
||||||
public $palette;
|
protected $palette;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new chart
|
* Create a new chart object and create internal objects
|
||||||
*
|
*
|
||||||
* See self::isValidDataFormat() for more information on how each series need to be structured
|
* If you want to extend this class use the init() method as an extension point,
|
||||||
*
|
* as this will be called at the end o fthe construct call
|
||||||
* @param array $dataSet, ... unlimited number of series to plot
|
|
||||||
*
|
|
||||||
* @see self::isValidDataFormat()
|
|
||||||
*/
|
*/
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->legend = new Legend();
|
$this->legend = new Legend();
|
||||||
$this->palette = new Palette();
|
$this->palette = new Palette();
|
||||||
$this->renderer = new SVGRenderer(2,1);
|
|
||||||
$this->init();
|
$this->init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension point for subclasses, called on __construct
|
||||||
|
*
|
||||||
|
*/
|
||||||
protected function init()
|
protected function init()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set up the legend for this chart
|
* Extension point for implementing rendering logic
|
||||||
*/
|
*
|
||||||
protected function setupLegend()
|
* This method is called after data validation, but before toSvg is called
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set up the elements for this chart
|
|
||||||
*/
|
*/
|
||||||
protected function build()
|
protected function build()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the current dataset has the proper structure for this chart
|
* Check if the current dataset has the proper structure for this chart.
|
||||||
*
|
*
|
||||||
* Needs to be overwritten by extending classes. The default implementation returns false.
|
* Needs to be overwritten by extending classes. The default implementation returns false.
|
||||||
*
|
*
|
||||||
* @return bool Whether the dataset is valid or not
|
* @return bool True when the dataset is valid, otherwise false
|
||||||
*/
|
*/
|
||||||
public function isValidDataFormat()
|
abstract public function isValidDataFormat();
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disable the legend for this chart
|
* Disable the legend for this chart
|
||||||
@ -130,7 +109,15 @@ abstract class Chart implements Drawable
|
|||||||
$this->legend = null;
|
$this->legend = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Render this graph and return the created SVG
|
||||||
|
*
|
||||||
|
* @return string The SVG created by the SvgRenderer
|
||||||
|
*
|
||||||
|
* @throws Exception Thrown wen the dataset is not valid for this graph
|
||||||
|
* @see SVGRenderer::render
|
||||||
|
*/
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
if (!$this->isValidDataFormat()) {
|
if (!$this->isValidDataFormat()) {
|
||||||
@ -141,8 +128,4 @@ abstract class Chart implements Drawable
|
|||||||
$this->renderer->getCanvas()->addElement($this);
|
$this->renderer->getCanvas()->addElement($this);
|
||||||
return $this->renderer->render();
|
return $this->renderer->render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -26,54 +26,81 @@
|
|||||||
*/
|
*/
|
||||||
// {{{ICINGA_LICENSE_HEADER}}}
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
|
||||||
|
|
||||||
namespace Icinga\Chart\Graph;
|
namespace Icinga\Chart\Graph;
|
||||||
|
|
||||||
|
use DOMElement;
|
||||||
|
|
||||||
|
use Icinga\Chart\Primitive\Animation;
|
||||||
use Icinga\Chart\Primitive\Drawable;
|
use Icinga\Chart\Primitive\Drawable;
|
||||||
use Icinga\Chart\Primitive\Rect;
|
use Icinga\Chart\Primitive\Rect;
|
||||||
use Icinga\Chart\Primitive\Styleable;
|
use Icinga\Chart\Primitive\Styleable;
|
||||||
use Icinga\Chart\Render\RenderContext;
|
use Icinga\Chart\Render\RenderContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bar graph implementation
|
||||||
|
*/
|
||||||
class BarGraph extends Styleable implements Drawable
|
class BarGraph extends Styleable implements Drawable
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* The dataset to use for this bar graph
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
private $dataSet;
|
private $dataSet;
|
||||||
public $fill = 'green';
|
|
||||||
|
/**
|
||||||
|
* Create a new BarGraph with the given dataset
|
||||||
|
*
|
||||||
|
* @param array $dataSet An array of datapoints
|
||||||
|
*/
|
||||||
public function __construct(array $dataSet)
|
public function __construct(array $dataSet)
|
||||||
{
|
{
|
||||||
$this->dataSet = $dataSet;
|
$this->dataSet = $dataSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply configuration styles from the $cfg
|
||||||
|
*
|
||||||
|
* @param array $cfg The configuration as given in the drawBars call
|
||||||
|
*/
|
||||||
|
public function setStyleFromConfig(array $cfg)
|
||||||
|
{
|
||||||
|
foreach ($cfg as $elem => $value) {
|
||||||
|
if ($elem === 'color') {
|
||||||
|
$this->setFill($value);
|
||||||
|
} elseif ($elem === 'width') {
|
||||||
|
$this->setStrokeWidth($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render this BarChart
|
||||||
|
*
|
||||||
|
* @param RenderContext $ctx The rendering context to use for drawing
|
||||||
|
*
|
||||||
|
* @return DOMElement $dom Element
|
||||||
|
*/
|
||||||
public function toSvg(RenderContext $ctx)
|
public function toSvg(RenderContext $ctx)
|
||||||
{
|
{
|
||||||
$doc = $ctx->getDocument();
|
$doc = $ctx->getDocument();
|
||||||
$group = $doc->createElement('g');
|
$group = $doc->createElement('g');
|
||||||
foreach($this->dataSet as $point)
|
foreach ($this->dataSet as $point) {
|
||||||
{
|
$rect = new Rect($point[0]-1, $point[1], 2, 100- $point[1]);
|
||||||
$rect = new Rect($point[0]-2, $point[1], 4, 100- $point[1]);
|
|
||||||
$rect->setFill($this->fill);
|
$rect->setFill($this->fill);
|
||||||
$rect->setStrokeWidth($this->strokeWidth);
|
$rect->setStrokeWidth($this->strokeWidth);
|
||||||
$rect->setStrokeColor($this->strokeColor);
|
$rect->setStrokeColor('black');
|
||||||
|
|
||||||
$rect->setAdditionalStyle('clip-path: url(#clip);');
|
$rect->setAdditionalStyle('clip-path: url(#clip);');
|
||||||
|
$rect->setAnimation(
|
||||||
|
new Animation(
|
||||||
|
'y',
|
||||||
|
$ctx->yToAbsolute(100),
|
||||||
|
$ctx->yToAbsolute($point[1]),
|
||||||
|
rand(1, 1.5)/2
|
||||||
|
)
|
||||||
|
);
|
||||||
$group->appendChild($rect->toSvg($ctx));
|
$group->appendChild($rect->toSvg($ctx));
|
||||||
}
|
}
|
||||||
return $group;
|
return $group;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setStyleFromConfig($cfg)
|
|
||||||
{
|
|
||||||
foreach ($cfg as $elem => $value) {
|
|
||||||
if ($elem === 'color') {
|
|
||||||
$this->setStrokeColor($value);
|
|
||||||
} else if ($elem === 'width') {
|
|
||||||
$this->setStrokeWidth($value);
|
|
||||||
} else if ($elem === 'showPoints') {
|
|
||||||
$this->setShowDataPoints($value);
|
|
||||||
} else if ($elem == 'fill') {
|
|
||||||
$this->setFill($value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -35,47 +35,80 @@ use Icinga\Chart\Primitive\Circle;
|
|||||||
use Icinga\Chart\Primitive\Styleable;
|
use Icinga\Chart\Primitive\Styleable;
|
||||||
use Icinga\Chart\Render\RenderContext;
|
use Icinga\Chart\Render\RenderContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LineGraph implementation for drawing a set of datapoints as
|
||||||
|
* a connected path
|
||||||
|
*/
|
||||||
class LineGraph extends Styleable implements Drawable
|
class LineGraph extends Styleable implements Drawable
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* The dataset to use
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $dataset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True to show dots for each datapoint
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
private $showDataPoints = false;
|
private $showDataPoints = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When true, the path will be discrete, i.e. showing hard steps instead of a direct line
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $isDiscrete = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default stroke width
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $strokeWidth = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new LineGraph displaying the given dataset
|
||||||
|
*
|
||||||
|
* @param array $dataset An array of [x, y] arrays to display
|
||||||
|
*/
|
||||||
public function __construct(array $dataset)
|
public function __construct(array $dataset)
|
||||||
{
|
{
|
||||||
usort($dataset, array($this, 'sortByX'));
|
usort($dataset, array($this, 'sortByX'));
|
||||||
$this->dataset = $dataset;
|
$this->dataset = $dataset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set datapoints to be emphased via dots
|
||||||
|
*
|
||||||
|
* @param $bool True to enable datapoints, otherwise false
|
||||||
|
*/
|
||||||
public function setShowDataPoints($bool)
|
public function setShowDataPoints($bool)
|
||||||
{
|
{
|
||||||
$this->showDataPoints = true;
|
$this->showDataPoints = $bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function sortByX(array $v1, array $v2) {
|
/**
|
||||||
|
* Sort the daset by the xaxis
|
||||||
|
*
|
||||||
|
* @param array $v1
|
||||||
|
* @param array $v2
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
private function sortByX(array $v1, array $v2)
|
||||||
|
{
|
||||||
if ($v1[0] === $v2[0]) {
|
if ($v1[0] === $v2[0]) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return ($v1[0] < $v2[0]) ? -1 : 1;
|
return ($v1[0] < $v2[0]) ? -1 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toSvg(RenderContext $ctx)
|
/**
|
||||||
{
|
* Configure this style
|
||||||
$path = new Path($this->dataset);
|
*
|
||||||
$path->setStrokeColor($this->strokeColor);
|
* @param array $cfg The configuration as given in the drawLine call
|
||||||
$path->setStrokeWidth($this->strokeWidth);
|
*/
|
||||||
$group = $path->toSvg($ctx);
|
public function setStyleFromConfig(array $cfg)
|
||||||
if ($this->showDataPoints === true) {
|
|
||||||
foreach ($this->dataset as $point) {
|
|
||||||
$dot = new Circle($point[0], $point[1], 25);
|
|
||||||
$dot->setFill($this->strokeColor);
|
|
||||||
$group->appendChild($dot->toSvg($ctx));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $group;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setStyleFromConfig($cfg)
|
|
||||||
{
|
{
|
||||||
|
$fill = false;
|
||||||
foreach ($cfg as $elem => $value) {
|
foreach ($cfg as $elem => $value) {
|
||||||
if ($elem === 'color') {
|
if ($elem === 'color') {
|
||||||
$this->setStrokeColor($value);
|
$this->setStrokeColor($value);
|
||||||
@ -83,8 +116,52 @@ class LineGraph extends Styleable implements Drawable
|
|||||||
$this->setStrokeWidth($value);
|
$this->setStrokeWidth($value);
|
||||||
} elseif ($elem === 'showPoints') {
|
} elseif ($elem === 'showPoints') {
|
||||||
$this->setShowDataPoints($value);
|
$this->setShowDataPoints($value);
|
||||||
|
} elseif ($elem === 'fill') {
|
||||||
|
$fill = $value;
|
||||||
|
} elseif ($elem === 'discrete') {
|
||||||
|
$this->isDiscrete = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ($fill) {
|
||||||
|
$this->setFill($this->strokeColor);
|
||||||
|
$this->setStrokeColor('black');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render this BarChart
|
||||||
|
*
|
||||||
|
* @param RenderContext $ctx The rendering context to use for drawing
|
||||||
|
*
|
||||||
|
* @return DOMElement $dom Element
|
||||||
|
*/
|
||||||
|
public function toSvg(RenderContext $ctx)
|
||||||
|
{
|
||||||
|
$path = new Path($this->dataset);
|
||||||
|
if ($this->isDiscrete) {
|
||||||
|
$path->setDiscrete(true);
|
||||||
|
}
|
||||||
|
$path->setStrokeColor($this->strokeColor);
|
||||||
|
$path->setStrokeWidth($this->strokeWidth);
|
||||||
|
|
||||||
|
if ($this->fill !== 'none') {
|
||||||
|
$firstX = $this->dataset[0][0];
|
||||||
|
$lastX = $this->dataset[count($this->dataset)-1][0];
|
||||||
|
$path->prepend(array($firstX, 100))
|
||||||
|
->append(array($lastX, 100));
|
||||||
|
$path->setFill($this->fill);
|
||||||
|
}
|
||||||
|
$path->setAdditionalStyle('clip-path: url(#clip);');
|
||||||
|
$path->setId($this->id);
|
||||||
|
$group = $path->toSvg($ctx);
|
||||||
|
if ($this->showDataPoints === true) {
|
||||||
|
foreach ($this->dataset as $point) {
|
||||||
|
$dot = new Circle($point[0], $point[1], $this->strokeWidth*5);
|
||||||
|
$dot->setFill('black');
|
||||||
|
|
||||||
|
$group->appendChild($dot->toSvg($ctx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $group;
|
||||||
|
}
|
||||||
}
|
}
|
108
library/Icinga/Chart/Graph/StackedGraph.php
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
<?php
|
||||||
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
/**
|
||||||
|
* This file is part of Icinga 2 Web.
|
||||||
|
*
|
||||||
|
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||||
|
* Copyright (C) 2013 Icinga Development Team
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*
|
||||||
|
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||||
|
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||||
|
* @author Icinga Development Team <info@icinga.org>
|
||||||
|
*/
|
||||||
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
|
||||||
|
namespace Icinga\Chart\Graph;
|
||||||
|
|
||||||
|
use Icinga\Chart\Primitive\Drawable;
|
||||||
|
use Icinga\Chart\Render\RenderContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Graph implementation that stacks several graphs and displays them in a cumulative way
|
||||||
|
*/
|
||||||
|
class StackedGraph implements Drawable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* All graphs displayed in this stackedgraph
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $stack = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An associative array containing x points as the key and an array of y values as the value
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $points = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a graph to this stack and aggregate the values on the fly
|
||||||
|
*
|
||||||
|
* This modifies the dataset as a side effect
|
||||||
|
*
|
||||||
|
* @param array $subGraph
|
||||||
|
*/
|
||||||
|
public function addGraph(array &$subGraph)
|
||||||
|
{
|
||||||
|
foreach ($subGraph['data'] as &$point) {
|
||||||
|
$x = $point[0];
|
||||||
|
if (!isset($this->points[$x])) {
|
||||||
|
$this->points[$x] = 0;
|
||||||
|
}
|
||||||
|
$this->points[$x] += $point[1];
|
||||||
|
$point[1] = $this->points[$x];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a graph to the stack
|
||||||
|
*
|
||||||
|
* @param $graph
|
||||||
|
*/
|
||||||
|
public function addToStack($graph)
|
||||||
|
{
|
||||||
|
$this->stack[] = $graph;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Empty the stack
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function stackEmpty()
|
||||||
|
{
|
||||||
|
return empty($this->stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render this stack in the correct order
|
||||||
|
*
|
||||||
|
* @param RenderContext $ctx The context to use for rendering
|
||||||
|
*
|
||||||
|
* @return DOMElement The SVG representation of this graph
|
||||||
|
*/
|
||||||
|
public function toSvg(RenderContext $ctx)
|
||||||
|
{
|
||||||
|
$group = $ctx->getDocument()->createElement('g');
|
||||||
|
$renderOrder = array_reverse($this->stack);
|
||||||
|
foreach ($renderOrder as $stackElem) {
|
||||||
|
$group->appendChild($stackElem->toSvg($ctx));
|
||||||
|
}
|
||||||
|
return $group;
|
||||||
|
}
|
||||||
|
}
|
@ -28,30 +28,87 @@
|
|||||||
|
|
||||||
namespace Icinga\Chart;
|
namespace Icinga\Chart;
|
||||||
|
|
||||||
|
use DOMElement;
|
||||||
|
|
||||||
use Icinga\Chart\Chart;
|
use Icinga\Chart\Chart;
|
||||||
use Icinga\Chart\Axis;
|
use Icinga\Chart\Axis;
|
||||||
use Icinga\Chart\Graph\BarGraph;
|
use Icinga\Chart\Graph\BarGraph;
|
||||||
use Icinga\Chart\Graph\LineGraph;
|
use Icinga\Chart\Graph\LineGraph;
|
||||||
|
use Icinga\Chart\Graph\StackedGraph;
|
||||||
use Icinga\Chart\Primitive\Canvas;
|
use Icinga\Chart\Primitive\Canvas;
|
||||||
use Icinga\Chart\Primitive\Rect;
|
use Icinga\Chart\Primitive\Rect;
|
||||||
use Icinga\Chart\Primitive\Path;
|
use Icinga\Chart\Primitive\Path;
|
||||||
use Icinga\Chart\Render\LayoutBox;
|
use Icinga\Chart\Render\LayoutBox;
|
||||||
use Icinga\Chart\Render\RenderContext;
|
use Icinga\Chart\Render\RenderContext;
|
||||||
|
use Icinga\Chart\Unit\AxisUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for grid based charts
|
* 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
|
class GridChart extends Chart
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Internal identifier for Line Chart elements
|
||||||
|
*/
|
||||||
const TYPE_LINE = "LINE";
|
const TYPE_LINE = "LINE";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal identifier fo Bar Chart elements
|
||||||
|
*/
|
||||||
const TYPE_BAR = "BAR";
|
const TYPE_BAR = "BAR";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal array containing all elements to be drawn in the order they are drawn
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
private $graphs = 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();
|
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();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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()
|
public function isValidDataFormat()
|
||||||
{
|
{
|
||||||
foreach ($this->graphs as $axis => $values) {
|
foreach ($this->graphs as $values) {
|
||||||
foreach ($values as $value) {
|
foreach ($values as $value) {
|
||||||
if (!isset($value['data']) || !is_array($value['data'])) {
|
if (!isset($value['data']) || !is_array($value['data'])) {
|
||||||
return false;
|
return false;
|
||||||
@ -61,29 +118,55 @@ class GridChart extends Chart
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls Axis::addDataset for every graph added to this GridChart
|
||||||
|
*/
|
||||||
private function configureAxisFromDatasets()
|
private function configureAxisFromDatasets()
|
||||||
{
|
{
|
||||||
foreach ($this->graphs as $axis => &$lines) {
|
foreach ($this->graphs as $axis => &$graphs) {
|
||||||
$axisObj = $this->axis[$axis];
|
$axisObj = $this->axis[$axis];
|
||||||
foreach ($lines as &$line) {
|
foreach ($graphs as &$graph) {
|
||||||
$axisObj->addDataset($line);
|
$axisObj->addDataset($graph);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an arbitary number of lines to be drawn
|
||||||
|
*
|
||||||
|
* Refer to the graphs.md for a detailed list of allowed attributes
|
||||||
|
*
|
||||||
|
* @param array $axix The line definitions to draw
|
||||||
|
* @param array ...
|
||||||
|
*
|
||||||
|
* @return self Fluid interface
|
||||||
|
*/
|
||||||
public function drawLines($axis /*,...*/)
|
public function drawLines($axis /*,...*/)
|
||||||
{
|
{
|
||||||
$this->draw(self::TYPE_LINE, func_get_args());
|
$this->draw(self::TYPE_LINE, func_get_args());
|
||||||
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add arbitary number of bars to be drawn
|
||||||
|
*
|
||||||
|
* Refer to the graphs.md for a detailed list of allowed attributes
|
||||||
|
*
|
||||||
|
* @param $axis
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
public function drawBars($axis)
|
public function drawBars($axis)
|
||||||
{
|
{
|
||||||
$this->draw(self::TYPE_BAR, func_get_args());
|
$this->draw(self::TYPE_BAR, func_get_args());
|
||||||
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic method for adding elements to the drawing stack
|
||||||
|
*
|
||||||
|
* @param Srring $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)
|
private function draw($type, $data)
|
||||||
{
|
{
|
||||||
$axisName = 'default';
|
$axisName = 'default';
|
||||||
@ -93,56 +176,160 @@ class GridChart extends Chart
|
|||||||
}
|
}
|
||||||
foreach ($data as &$graph) {
|
foreach ($data as &$graph) {
|
||||||
$graph['graphType'] = $type;
|
$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;
|
$this->graphs[$axisName][] = $graph;
|
||||||
|
if ($this->legend) {
|
||||||
$this->legend->addDataset($graph);
|
$this->legend->addDataset($graph);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 self Fluid interface
|
||||||
|
*/
|
||||||
public function setAxisLabel($xAxisLabel, $yAxisLabel, $axisName = 'default')
|
public function setAxisLabel($xAxisLabel, $yAxisLabel, $axisName = 'default')
|
||||||
{
|
{
|
||||||
$this->axis[$axisName]->setXLabel($xAxisLabel)->setYLabel($yAxisLabel);
|
$this->axis[$axisName]->setXLabel($xAxisLabel)->setYLabel($yAxisLabel);
|
||||||
return $this;
|
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 self 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 self 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()
|
protected function build()
|
||||||
{
|
{
|
||||||
$this->configureAxisFromDatasets();
|
$this->configureAxisFromDatasets();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the renderer and overwrite it with an 2:1 ration renderer
|
||||||
|
*/
|
||||||
protected function init()
|
protected function init()
|
||||||
{
|
{
|
||||||
|
$this->renderer = new SVGRenderer(2, 1);
|
||||||
$this->setAxis(Axis::createLinearAxis());
|
$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 self Fluid interface
|
||||||
|
*/
|
||||||
public function setAxis(Axis $axis, $name = 'default')
|
public function setAxis(Axis $axis, $name = 'default')
|
||||||
{
|
{
|
||||||
$this->axis = array($name => $axis);
|
$this->axis = array($name => $axis);
|
||||||
return $this;
|
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 self Fluid interface
|
||||||
|
*/
|
||||||
public function addAxis(Axis $axis, $name)
|
public function addAxis(Axis $axis, $name)
|
||||||
{
|
{
|
||||||
$this->axis[$name] = $axis;
|
$this->axis[$name] = $axis;
|
||||||
return $this;
|
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|null $xMin The minimum value for the x axis or null to use a dynamic value
|
||||||
|
* @param int|null $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 self Fluid interface
|
||||||
|
*/
|
||||||
public function setAxisMin($xMin = null, $yMin = null, $axisName = 'default')
|
public function setAxisMin($xMin = null, $yMin = null, $axisName = 'default')
|
||||||
{
|
{
|
||||||
$this->axis[$axisName]->setXMin($xMin)->setYMin($yMin);
|
$this->axis[$axisName]->setXMin($xMin)->setYMin($yMin);
|
||||||
return $this;
|
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|null $xMax The maximum value for the x axis or null to use a dynamic value
|
||||||
|
* @param int|null $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 self Fluid interface
|
||||||
|
*/
|
||||||
public function setAxisMax($xMax = null, $yMax = null, $axisName = 'default')
|
public function setAxisMax($xMax = null, $yMax = null, $axisName = 'default')
|
||||||
{
|
{
|
||||||
$this->axis[$axisName]->setXMax($xMax)->setYMax($yMax);
|
$this->axis[$axisName]->setXMax($xMax)->setYMax($yMax);
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render this GridChart to SVG
|
||||||
|
*
|
||||||
|
* @param RenderContext $ctx The context to use for rendering
|
||||||
|
*
|
||||||
|
* @return DOMElement
|
||||||
|
*/
|
||||||
public function toSvg(RenderContext $ctx)
|
public function toSvg(RenderContext $ctx)
|
||||||
{
|
{
|
||||||
$outerBox = new Canvas('outerGraph', new LayoutBox(0, 0, 100, 100));
|
$outerBox = new Canvas('outerGraph', new LayoutBox(0, 0, 100, 100));
|
||||||
$innerBox = new Canvas('graph', new LayoutBox(0, 0, 100, 90));
|
$innerBox = new Canvas('graph', new LayoutBox(0, 0, 95, 90));
|
||||||
|
|
||||||
$maxPadding = array(0,0,0,0);
|
$maxPadding = array(0,0,0,0);
|
||||||
foreach ($this->axis as $axis) {
|
foreach ($this->axis as $axis) {
|
||||||
@ -155,44 +342,82 @@ class GridChart extends Chart
|
|||||||
$this->renderGraphContent($innerBox);
|
$this->renderGraphContent($innerBox);
|
||||||
|
|
||||||
$innerBox->getLayout()->setPadding($maxPadding[0], $maxPadding[1], $maxPadding[2], $maxPadding[3]);
|
$innerBox->getLayout()->setPadding($maxPadding[0], $maxPadding[1], $maxPadding[2], $maxPadding[3]);
|
||||||
$clipBox = new Canvas('clip', new LayoutBox(0,0,100,100));
|
$this->createContentClipBox($innerBox);
|
||||||
$clipBox->toClipPath();
|
|
||||||
$innerBox->addElement($clipBox);
|
|
||||||
|
|
||||||
$clipBox->addElement(new Rect(0,0,100,100));
|
|
||||||
$outerBox->addElement($innerBox);
|
$outerBox->addElement($innerBox);
|
||||||
|
if ($this->legend) {
|
||||||
$outerBox->addElement($this->legend);
|
$outerBox->addElement($this->legend);
|
||||||
|
}
|
||||||
return $outerBox->toSvg($ctx);
|
return $outerBox->toSvg($ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function renderGraphContent($innerBox)
|
/**
|
||||||
|
* 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) {
|
foreach ($this->graphs as $axisName => $graphs) {
|
||||||
$axis = $this->axis[$axisName];
|
$axis = $this->axis[$axisName];
|
||||||
foreach ($graphs as $graph) {
|
foreach ($graphs as $graph) {
|
||||||
|
// determine the type and create a graph object for it
|
||||||
switch ($graph['graphType']) {
|
switch ($graph['graphType']) {
|
||||||
case self::TYPE_BAR:
|
case self::TYPE_BAR:
|
||||||
$this->renderBars($axis, $graph, $innerBox);
|
$graphObj = new BarGraph($axis->transform($graph['data']));
|
||||||
break;
|
break;
|
||||||
case self::TYPE_LINE:
|
case self::TYPE_LINE:
|
||||||
$this->renderLines($axis, $graph, $innerBox);
|
$graphObj = new LineGraph($axis->transform($graph['data']));
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$el = $this->setupGraph($graphObj, $graph);
|
||||||
|
if ($el) {
|
||||||
|
$innerBox->addElement($el);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function renderLines($axis, array $graph, Canvas $innerBox)
|
/**
|
||||||
|
* 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 null|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)
|
||||||
{
|
{
|
||||||
$path = new LineGraph($axis->transform($graph['data']));
|
$graphObject->setStyleFromConfig($graphConfig);
|
||||||
$path->setStyleFromConfig($graph);
|
// When in a stack return the StackedGraph object instead of the graphObject
|
||||||
$innerBox->addElement($path);
|
if (isset($graphConfig['stack'])) {
|
||||||
|
$graphConfig['stack']->addToStack($graphObject);
|
||||||
|
if (!$graphConfig['stack']->stackEmpty()) {
|
||||||
|
return $graphConfig['stack'];
|
||||||
}
|
}
|
||||||
|
// return no object when the graph shouldn be rendered
|
||||||
private function renderBars($axis, array $graph, Canvas $innerBox)
|
return null;
|
||||||
{
|
}
|
||||||
$path = new BarGraph($axis->transform($graph['data']));
|
return $graphObject;
|
||||||
$innerBox->addElement($path);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,8 @@
|
|||||||
|
|
||||||
namespace Icinga\Chart;
|
namespace Icinga\Chart;
|
||||||
|
|
||||||
|
use DOMElement;
|
||||||
|
|
||||||
use Icinga\Chart\Palette;
|
use Icinga\Chart\Palette;
|
||||||
use Icinga\Chart\Primitive\Canvas;
|
use Icinga\Chart\Primitive\Canvas;
|
||||||
use Icinga\Chart\Primitive\Drawable;
|
use Icinga\Chart\Primitive\Drawable;
|
||||||
@ -35,11 +37,22 @@ use Icinga\Chart\Primitive\Rect;
|
|||||||
use Icinga\Chart\Primitive\Text;
|
use Icinga\Chart\Primitive\Text;
|
||||||
use Icinga\Chart\Render\LayoutBox;
|
use Icinga\Chart\Render\LayoutBox;
|
||||||
use Icinga\Chart\Render\RenderContext;
|
use Icinga\Chart\Render\RenderContext;
|
||||||
use Icinga\Chart\SVGRenderer;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drawable for creating a Graph Legend on the bottom of a graph.
|
||||||
|
*
|
||||||
|
* Usually used by the GridChart class internally.
|
||||||
|
*/
|
||||||
class Legend implements Drawable
|
class Legend implements Drawable
|
||||||
{
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal counter for unnamed label identifiers
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
private $internalCtr = 0;
|
private $internalCtr = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Content of this legend
|
* Content of this legend
|
||||||
@ -48,76 +61,66 @@ class Legend implements Drawable
|
|||||||
*/
|
*/
|
||||||
private $dataset = array();
|
private $dataset = array();
|
||||||
|
|
||||||
/**
|
|
||||||
* Maximum space this legend can take in percent
|
|
||||||
*
|
|
||||||
* Is intelligently applied depending on the location of the legend.
|
|
||||||
*
|
|
||||||
* @var float
|
|
||||||
*/
|
|
||||||
public $maxVolume = 0.05;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Where this legend is displayed
|
|
||||||
*
|
|
||||||
* Possible values are: left, right, top, bottom
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public $location = 'bottom';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The style-palette this legend is using
|
|
||||||
*
|
|
||||||
* @var Palette
|
|
||||||
*/
|
|
||||||
public $palette;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new legend
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the content to be displayed by this legend
|
* Set the content to be displayed by this legend
|
||||||
*
|
*
|
||||||
* @param array $data Array of key-value pairs representing the labels and their colour
|
* @param array $dataset An array of datasets in the form they are provided to the graphing implementation
|
||||||
*/
|
*/
|
||||||
public function addDataset(array $dataset)
|
public function addDataset(array $dataset)
|
||||||
{
|
{
|
||||||
if (!isset($dataset['label'])) {
|
if (!isset($dataset['label'])) {
|
||||||
$dataset['label'] = 'Dataset ' . (++$this->internalCtr);
|
$dataset['label'] = 'Dataset ' . (++$this->internalCtr);
|
||||||
}
|
}
|
||||||
|
if (!isset($dataset['color'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
$this->dataset[$dataset['color']] = $dataset['label'];
|
$this->dataset[$dataset['color']] = $dataset['label'];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function toSvg(RenderContext $ctx)
|
/**
|
||||||
|
* Render the legend to an SVG object
|
||||||
|
*
|
||||||
|
* @param RenderContext $ctx The context to use for rendering this legend
|
||||||
|
*
|
||||||
|
* @return DOMElement The SVG representation of this legend
|
||||||
|
*/
|
||||||
|
public function toSvg(RenderContext $ctx)
|
||||||
{
|
{
|
||||||
$outer = new Canvas('legend', new LayoutBox(0, 95, 100, 100));
|
$outer = new Canvas('legend', new LayoutBox(0, 90, 100, 100));
|
||||||
$outer->getLayout()->setPadding(2,10,2,10);
|
$outer->getLayout()->setPadding(2, 2, 2, 2);
|
||||||
|
$nrOfColumns = 4;
|
||||||
|
|
||||||
|
$leftstep = 100 / $nrOfColumns;
|
||||||
|
$topstep = 10 / $nrOfColumns + 2;
|
||||||
|
|
||||||
$step = 100/count($this->dataset);
|
|
||||||
$top = 0;
|
$top = 0;
|
||||||
|
$left = 0;
|
||||||
|
$lastLabelEndPos = -1;
|
||||||
foreach ($this->dataset as $color => $text) {
|
foreach ($this->dataset as $color => $text) {
|
||||||
$colorBox = new Rect($top, 0, 2, 2);
|
// Make sure labels don't overlap each other
|
||||||
$colorBox->setFill($color);
|
while ($lastLabelEndPos >= $left) {
|
||||||
$colorBox->setStrokeWidth(2);
|
$left += $leftstep;
|
||||||
$outer->addElement($colorBox);
|
}
|
||||||
$textBox = new Text($top+5 , 1.8 , $text);
|
// When a label is longer than the available space, use the next line
|
||||||
$outer->addElement($textBox);
|
if ($left + strlen($text) > 100) {
|
||||||
$top += $step;
|
$top += $topstep;
|
||||||
|
$left = 0;
|
||||||
}
|
}
|
||||||
$ctx->keepRatio();
|
|
||||||
$svg = $outer->toSvg($ctx);
|
|
||||||
$ctx->ignoreRatio();
|
|
||||||
|
|
||||||
|
$colorBox = new Rect($left, $top, 2, 2);
|
||||||
|
$colorBox->setFill($color)->setStrokeWidth(2)->keepRatio();
|
||||||
|
$outer->addElement($colorBox);
|
||||||
|
|
||||||
|
$textBox = new Text($left+5, $top+2, $text);
|
||||||
|
$textBox->setFontSize('2em');
|
||||||
|
$outer->addElement($textBox);
|
||||||
|
|
||||||
|
// readjust layout
|
||||||
|
$lastLabelEndPos = $left + strlen($text);
|
||||||
|
$left += $leftstep;
|
||||||
|
}
|
||||||
|
$svg = $outer->toSvg($ctx);
|
||||||
return $svg;
|
return $svg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -5,11 +5,61 @@
|
|||||||
namespace Icinga\Chart;
|
namespace Icinga\Chart;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define the overall style of a chart
|
* Provide a set of colors that will be used by the chart as default values
|
||||||
*/
|
*/
|
||||||
class Palette
|
class Palette
|
||||||
{
|
{
|
||||||
/*
|
/**
|
||||||
* TODO: Font, padding, margin, colors, borders, ticks, symbols, ...
|
* Neutral colors without special meaning
|
||||||
*/
|
*/
|
||||||
|
const NEUTRAL = 'neutral';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A set of problem (i.e. red) colors
|
||||||
|
*/
|
||||||
|
const PROBLEM = 'problem';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A set of ok (i.e. green) colors
|
||||||
|
*/
|
||||||
|
const OK = 'ok';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A set of warning (i.e. yellow) colors
|
||||||
|
*/
|
||||||
|
const WARNING = 'warning';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The colorsets for specific categories
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public $colorSets = array(
|
||||||
|
self::OK => array('#00FF00','#00C90D', '#008209', '#238C47', '#00BB3F', '#37DD6F'),
|
||||||
|
self::PROBLEM => array('#FF0000','#FF1300', '#FF4E40', '#A60C00', '#FF4500', '#A62D00'),
|
||||||
|
self::WARNING => array('#FFFF00', 'B4B400' , '#A6A600', '#F5FF73', '#FFB300', '#BFA730'),
|
||||||
|
self::NEUTRAL => array('#232323', '#009999', '#1D7373', '#ACACFF', '#8F9ABF', '#356AA6')
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the next available color as an hex string for the given type
|
||||||
|
*
|
||||||
|
* @param string $type The type to receive a color from
|
||||||
|
*
|
||||||
|
* @return string The color in hex format
|
||||||
|
*/
|
||||||
|
public function getNext($type = self::NEUTRAL)
|
||||||
|
{
|
||||||
|
if (!isset($this->colorSets[$type])) {
|
||||||
|
$type = self::NEUTRAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
$color = current($this->colorSets[$type]);
|
||||||
|
if ($color === false) {
|
||||||
|
reset($this->colorSets[$type]);
|
||||||
|
$color = current($this->colorSets[$type]);
|
||||||
|
}
|
||||||
|
next($this->colorSets[$type]);
|
||||||
|
return $color;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,281 @@
|
|||||||
|
|
||||||
namespace Icinga\Chart;
|
namespace Icinga\Chart;
|
||||||
|
|
||||||
use Icinga\Chart\Chart;
|
use DOMElement;
|
||||||
|
|
||||||
|
use Icinga\Chart\Chart;
|
||||||
|
use Icinga\Chart\Primitive\Canvas;
|
||||||
|
use Icinga\Chart\Primitive\PieSlice;
|
||||||
|
use Icinga\Chart\Primitive\RawElement;
|
||||||
|
use Icinga\Chart\Primitive\Rect;
|
||||||
|
use Icinga\Chart\Render\RenderContext;
|
||||||
|
use Icinga\Chart\Render\LayoutBox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Graphing component for rendering Pie Charts.
|
||||||
|
*
|
||||||
|
* See the graphs.md documentation for futher information about how to use this component
|
||||||
|
*/
|
||||||
class PieChart extends Chart
|
class PieChart extends Chart
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Stack multiple pies
|
||||||
|
*/
|
||||||
|
const STACKED = "stacked";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw multiple pies beneath each other
|
||||||
|
*/
|
||||||
|
const ROW = "row";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The drawing stack containing all pie definitions in the order they will be drawn
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $pies = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The composition type currently used
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $type = PieChart::STACKED;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable drawing of captions when set true
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $noCaption = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if the given pies have the correct format
|
||||||
|
*
|
||||||
|
* @return bool True when the given pies are correct, otherwise false
|
||||||
|
*/
|
||||||
|
public function isValidDataFormat()
|
||||||
|
{
|
||||||
|
foreach ($this->pies as $pie) {
|
||||||
|
if (!isset($pie['data']) || !is_array($pie['data'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create renderer and normalize the dataset to represent percentage information
|
||||||
|
*/
|
||||||
|
protected function build()
|
||||||
|
{
|
||||||
|
$this->renderer = new SVGRenderer(($this->type === self::STACKED) ? 1 : count($this->pies), 1);
|
||||||
|
foreach ($this->pies as &$pie) {
|
||||||
|
$this->normalizeDataSet($pie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize the given dataset to represent percentage information instead of absolute valuess
|
||||||
|
*
|
||||||
|
* @param array $pie The pie definition given in the drawPie call
|
||||||
|
*/
|
||||||
|
private function normalizeDataSet(&$pie)
|
||||||
|
{
|
||||||
|
$total = array_sum($pie['data']);
|
||||||
|
if ($total === 100) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foreach ($pie['data'] as &$slice) {
|
||||||
|
$slice = $slice/$total * 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw an arbitary number of pies in this chart
|
||||||
|
*
|
||||||
|
* @param $dataSet The pie definition, see graphs.md for further details concerning the format
|
||||||
|
* @param ...
|
||||||
|
*
|
||||||
|
* @return self Fluent interface
|
||||||
|
*/
|
||||||
|
public function drawPie($dataSet)
|
||||||
|
{
|
||||||
|
$dataSets = func_get_args();
|
||||||
|
$this->pies += $dataSets;
|
||||||
|
foreach ($dataSets as $dataSet) {
|
||||||
|
$this->legend->addDataset($dataSet);
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the SVG representation of this graph
|
||||||
|
*
|
||||||
|
* @param RenderContext $ctx The context to use for drawings
|
||||||
|
*
|
||||||
|
* @return DOMElement The SVG representation of this graph
|
||||||
|
*/
|
||||||
|
public function toSvg(RenderContext $ctx)
|
||||||
|
{
|
||||||
|
$outerBox = new Canvas('outerGraph', new LayoutBox(0, 0, 100, 100));
|
||||||
|
$innerBox = new Canvas('graph', new LayoutBox(0, 0, 100, 100));
|
||||||
|
$labelBox = $ctx->getDocument()->createElement('g');
|
||||||
|
$innerBox->getLayout()->setPadding(10, 10, 10, 10);
|
||||||
|
$this->createContentClipBox($innerBox);
|
||||||
|
$this->renderPies($innerBox, $labelBox);
|
||||||
|
$innerBox->addElement(new RawElement($labelBox));
|
||||||
|
$outerBox->addElement($innerBox);
|
||||||
|
|
||||||
|
return $outerBox->toSvg($ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the pies in the draw stack using the selected algorithm for composition
|
||||||
|
*
|
||||||
|
* @param Canvas $innerBox The canvas to use for inserting the pies
|
||||||
|
* @param DOMElement $labelBox The DOM element to add the labels to (so they can't be overlapped by pie elements)
|
||||||
|
*/
|
||||||
|
private function renderPies(Canvas $innerBox, DOMElement $labelBox)
|
||||||
|
{
|
||||||
|
if ($this->type === self::STACKED) {
|
||||||
|
$this->renderStackedPie($innerBox, $labelBox);
|
||||||
|
} else {
|
||||||
|
$this->renderPieRow($innerBox, $labelBox);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the color to be used for the given pie slice
|
||||||
|
*
|
||||||
|
* @param array $pie The pie configuration as provided in the drawPie call
|
||||||
|
* @param int $dataIdx The index of the pie slice in the pie configuration
|
||||||
|
*
|
||||||
|
* @return string The hex color string to use for the pie slice
|
||||||
|
*/
|
||||||
|
private function getColorForPieSlice(array $pie, $dataIdx)
|
||||||
|
{
|
||||||
|
if (isset($pie['colors']) && is_array($pie['colors']) && isset($pie['colors'][$dataIdx])) {
|
||||||
|
return $pie['colors']['dataIdx'];
|
||||||
|
}
|
||||||
|
$type = Palette::NEUTRAL;
|
||||||
|
if (isset($pie['palette']) && is_array($pie['palette']) && isset($pie['palette'][$dataIdx])) {
|
||||||
|
$type = $pie['palette'][$dataIdx];
|
||||||
|
}
|
||||||
|
return $this->palette->getNext($type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a row of pies
|
||||||
|
*
|
||||||
|
* @param Canvas $innerBox The canvas to insert the pies to
|
||||||
|
* @param DOMElement $labelBox The DOMElement to use for adding label elements
|
||||||
|
*/
|
||||||
|
private function renderPieRow(Canvas $innerBox, DOMElement $labelBox)
|
||||||
|
{
|
||||||
|
$radius = 50 / count($this->pies);
|
||||||
|
$x = $radius;
|
||||||
|
foreach ($this->pies as $pie) {
|
||||||
|
$labelPos = 0;
|
||||||
|
$lastRadius = 0;
|
||||||
|
foreach ($pie['data'] as $idx => $dataset) {
|
||||||
|
$slice = new PieSlice($radius, $dataset, $lastRadius);
|
||||||
|
$slice->setX($x)
|
||||||
|
->setStrokeColor('#000')
|
||||||
|
->setStrokeWidth(1)
|
||||||
|
->setY(50)
|
||||||
|
->setFill($this->getColorForPieSlice($pie, $idx));
|
||||||
|
$innerBox->addElement($slice);
|
||||||
|
// add caption if not disabled
|
||||||
|
if (!$this->noCaption) {
|
||||||
|
$slice->setCaption($pie['labels'][$labelPos++])
|
||||||
|
->setLabelGroup($labelBox);
|
||||||
|
|
||||||
|
}
|
||||||
|
$lastRadius += $dataset;
|
||||||
|
}
|
||||||
|
// shift right for next pie
|
||||||
|
$x += $radius*2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render pies in a stacked way so one pie is nested in the previous pie
|
||||||
|
*
|
||||||
|
* @param Canvas $innerBox The canvas to insert the pie to
|
||||||
|
* @param DOMElement $labelBox The DOMElement to use for adding label elements
|
||||||
|
*/
|
||||||
|
private function renderStackedPie(Canvas $innerBox, DOMElement $labelBox)
|
||||||
|
{
|
||||||
|
$radius = 50;
|
||||||
|
$minRadius = 20;
|
||||||
|
$shrinkStep = ($radius - $minRadius) / count($this->pies);
|
||||||
|
$x = $radius;
|
||||||
|
|
||||||
|
for ($i = 0; $i < count($this->pies); $i++) {
|
||||||
|
$pie = $this->pies[$i];
|
||||||
|
// the offset for the caption path, outer caption indicator shouldn't point
|
||||||
|
// to the middle of the slice as there will be another pie
|
||||||
|
$offset = isset($this->pies[$i+1]) ? $radius - $shrinkStep : 0;
|
||||||
|
$labelPos = 0;
|
||||||
|
$lastRadius = 0;
|
||||||
|
foreach ($pie['data'] as $idx => $dataset) {
|
||||||
|
$slice = new PieSlice($radius, $dataset, $lastRadius);
|
||||||
|
$slice->setY(50)
|
||||||
|
->setX($x)
|
||||||
|
->setStrokeColor('#000')
|
||||||
|
->setStrokeWidth(1)
|
||||||
|
->setFill($this->getColorForPieSlice($pie, $idx))
|
||||||
|
->setLabelGroup($labelBox);
|
||||||
|
|
||||||
|
if (!$this->noCaption) {
|
||||||
|
$slice->setCaption($pie['labels'][$labelPos++])
|
||||||
|
->setCaptionOffset($offset)
|
||||||
|
->setOuterCaptionBound(50);
|
||||||
|
}
|
||||||
|
$innerBox->addElement($slice);
|
||||||
|
$lastRadius += $dataset;
|
||||||
|
}
|
||||||
|
// shrinken the next pie
|
||||||
|
$radius -= $shrinkStep;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the composition type of this PieChart
|
||||||
|
*
|
||||||
|
* @param string $type Either self::STACKED or self::ROW
|
||||||
|
*
|
||||||
|
* @return self Fluent interface
|
||||||
|
*/
|
||||||
|
public function setType($type)
|
||||||
|
{
|
||||||
|
$this->type = $type;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the caption from this PieChart
|
||||||
|
*
|
||||||
|
* @return self Fluent interface
|
||||||
|
*/
|
||||||
|
public function disableLegend()
|
||||||
|
{
|
||||||
|
$this->noCaption = true;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the content for this PieChart
|
||||||
|
*
|
||||||
|
* @param Canvas $innerBox The innerbox to add the clip mask 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
68
library/Icinga/Chart/Primitive/Animatable.php
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
/**
|
||||||
|
* This file is part of Icinga 2 Web.
|
||||||
|
*
|
||||||
|
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||||
|
* Copyright (C) 2013 Icinga Development Team
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*
|
||||||
|
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||||
|
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||||
|
* @author Icinga Development Team <info@icinga.org>
|
||||||
|
*/
|
||||||
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
|
||||||
|
namespace Icinga\Chart\Primitive;
|
||||||
|
|
||||||
|
use DOMElement;
|
||||||
|
use Icinga\Chart\Render\RenderContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base interace for animatable objects
|
||||||
|
*/
|
||||||
|
abstract class Animatable extends Styleable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The animation object set
|
||||||
|
*
|
||||||
|
* @var Animation
|
||||||
|
*/
|
||||||
|
public $animation = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the animation for this object
|
||||||
|
*
|
||||||
|
* @param Animation $anim The animation to use
|
||||||
|
*/
|
||||||
|
public function setAnimation(Animation $anim)
|
||||||
|
{
|
||||||
|
$this->animation = $anim;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append the animation to the given element
|
||||||
|
*
|
||||||
|
* @param DOMElement $dom The element to append the animstion to
|
||||||
|
* @param RenderContext $ctx The context to use for rendering the animation object
|
||||||
|
*/
|
||||||
|
protected function appendAnimation(DOMElement $dom, RenderContext $ctx)
|
||||||
|
{
|
||||||
|
if ($this->animation) {
|
||||||
|
$dom->appendChild($this->animation->toSvg($ctx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
111
library/Icinga/Chart/Primitive/Animation.php
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
<?php
|
||||||
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
/**
|
||||||
|
* This file is part of Icinga 2 Web.
|
||||||
|
*
|
||||||
|
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||||
|
* Copyright (C) 2013 Icinga Development Team
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*
|
||||||
|
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||||
|
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||||
|
* @author Icinga Development Team <info@icinga.org>
|
||||||
|
*/
|
||||||
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
|
||||||
|
namespace Icinga\Chart\Primitive;
|
||||||
|
|
||||||
|
use DOMElement;
|
||||||
|
use Icinga\Chart\Render\RenderContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drawable for the SVG animate tag
|
||||||
|
*/
|
||||||
|
class Animation implements Drawable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The attribute to animate
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $attribute;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The 'from' value
|
||||||
|
*
|
||||||
|
* @var mixed
|
||||||
|
*/
|
||||||
|
private $from;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The to value
|
||||||
|
*
|
||||||
|
* @var mixed
|
||||||
|
*/
|
||||||
|
private $to;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The begin value (in seconds)
|
||||||
|
*
|
||||||
|
* @var float
|
||||||
|
*/
|
||||||
|
private $begin = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The duration value (in seconds)
|
||||||
|
*
|
||||||
|
* @var float
|
||||||
|
*/
|
||||||
|
private $duration = 0.5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an animation object
|
||||||
|
*
|
||||||
|
* @param string $attribute The attribute to animate
|
||||||
|
* @param string $from The from value for the animation
|
||||||
|
* @param string $to The to value for the animation
|
||||||
|
* @param float $duration The duration of the duration
|
||||||
|
* @param float $begin The begin of the duration
|
||||||
|
*/
|
||||||
|
public function __construct($attribute, $from, $to, $duration = 0.5, $begin = 0)
|
||||||
|
{
|
||||||
|
$this->attribute = $attribute;
|
||||||
|
$this->from = $from;
|
||||||
|
$this->to = $to;
|
||||||
|
$this->duration = $duration;
|
||||||
|
$this->begin = $begin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the SVG representation from this Drawable
|
||||||
|
*
|
||||||
|
* @param RenderContext $ctx The context to use for rendering
|
||||||
|
* @return DOMElement The SVG Element
|
||||||
|
*/
|
||||||
|
public function toSvg(RenderContext $ctx)
|
||||||
|
{
|
||||||
|
|
||||||
|
$animate = $ctx->getDocument()->createElement('animate');
|
||||||
|
$animate->setAttribute('attributeName', $this->attribute);
|
||||||
|
$animate->setAttribute('attributeType', 'XML');
|
||||||
|
$animate->setAttribute('from', $this->from);
|
||||||
|
$animate->setAttribute('to', $this->to);
|
||||||
|
$animate->setAttribute('begin', $this->begin . 's');
|
||||||
|
$animate->setAttribute('dur', $this->duration . 's');
|
||||||
|
$animate->setAttributE('fill', "freeze");
|
||||||
|
|
||||||
|
return $animate;
|
||||||
|
}
|
||||||
|
}
|
@ -32,26 +32,85 @@ namespace Icinga\Chart\Primitive;
|
|||||||
use Icinga\Chart\Render\LayoutBox;
|
use Icinga\Chart\Render\LayoutBox;
|
||||||
use Icinga\Chart\Render\RenderContext;
|
use Icinga\Chart\Render\RenderContext;
|
||||||
|
|
||||||
use Icinga\Util\Dimension;
|
/**
|
||||||
|
* Canvas SVG component that encapsulates grouping and padding and allows rendering
|
||||||
class Canvas implements Drawable {
|
* multiple elements in a group
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class Canvas implements Drawable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name of the canvas, will be used as the id
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
private $name;
|
private $name;
|
||||||
private $children = array();
|
|
||||||
private $isClipPath = false;
|
|
||||||
private $keepRatio = false;
|
|
||||||
|
|
||||||
public function __construct($name, LayoutBox $rect) {
|
/**
|
||||||
|
* An array of child elements of this Canvas
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $children = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When true, this canvas is encapsulated in a clipPath tag and not drawn
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $isClipPath = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The LayoutBox of this Canvas
|
||||||
|
* @var LayoutBox
|
||||||
|
*/
|
||||||
|
private $rect;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create this canvas
|
||||||
|
*
|
||||||
|
* @param String $name The name of this canvas
|
||||||
|
* @param LayoutBox $rect The layout and size of this canvas
|
||||||
|
*/
|
||||||
|
public function __construct($name, LayoutBox $rect)
|
||||||
|
{
|
||||||
$this->rect = $rect;
|
$this->rect = $rect;
|
||||||
$this->name = $name;
|
$this->name = $name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert this canvas to a clipPath element
|
||||||
|
*/
|
||||||
public function toClipPath()
|
public function toClipPath()
|
||||||
{
|
{
|
||||||
$this->isClipPath = true;
|
$this->isClipPath = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toSvg(RenderContext $ctx) {
|
/**
|
||||||
|
* Return the layout of this canvas
|
||||||
|
*
|
||||||
|
* @return LayoutBox
|
||||||
|
*/
|
||||||
|
public function getLayout()
|
||||||
|
{
|
||||||
|
return $this->rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an element to this canvas
|
||||||
|
*
|
||||||
|
* @param Drawable $child
|
||||||
|
*/
|
||||||
|
public function addElement(Drawable $child)
|
||||||
|
{
|
||||||
|
$this->children[] = $child;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the SVG representation from this Drawable
|
||||||
|
*
|
||||||
|
* @param RenderContext $ctx The context to use for rendering
|
||||||
|
* @return DOMElement The SVG Element
|
||||||
|
*/
|
||||||
|
public function toSvg(RenderContext $ctx)
|
||||||
|
{
|
||||||
$doc = $ctx->getDocument();
|
$doc = $ctx->getDocument();
|
||||||
if ($this->isClipPath) {
|
if ($this->isClipPath) {
|
||||||
$outer = $doc->createElement('defs');
|
$outer = $doc->createElement('defs');
|
||||||
@ -68,21 +127,10 @@ class Canvas implements Drawable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$element->setAttribute('id', $this->name);
|
$element->setAttribute('id', $this->name);
|
||||||
$element->setAttribute('transform', $this->rect->getOuterTranslate($ctx));
|
|
||||||
|
|
||||||
foreach ($this->children as $child) {
|
foreach ($this->children as $child) {
|
||||||
$innerContainer->appendChild($child->toSvg($ctx));
|
$innerContainer->appendChild($child->toSvg($ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $outer;
|
return $outer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function getLayout() {
|
|
||||||
return $this->rect;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function addElement(Drawable $child) {
|
|
||||||
$this->children[] = $child;
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -29,17 +29,38 @@
|
|||||||
|
|
||||||
namespace Icinga\Chart\Primitive;
|
namespace Icinga\Chart\Primitive;
|
||||||
|
|
||||||
|
|
||||||
use Icinga\Chart\Render\RenderContext;
|
use Icinga\Chart\Render\RenderContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drawable for svg circles
|
||||||
|
*/
|
||||||
class Circle extends Styleable implements Drawable
|
class Circle extends Styleable implements Drawable
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* The circles x position
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
private $x;
|
private $x;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The circles y position
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
private $y;
|
private $y;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The circles radius
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
private $radius;
|
private $radius;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct the circle
|
||||||
|
*
|
||||||
|
* @param int $x The x position of the circle
|
||||||
|
* @param int $y The y position of the circle
|
||||||
|
* @param int $radius The radius of the circle
|
||||||
|
*/
|
||||||
public function __construct($x, $y, $radius)
|
public function __construct($x, $y, $radius)
|
||||||
{
|
{
|
||||||
$this->x = $x;
|
$this->x = $x;
|
||||||
@ -47,7 +68,12 @@ class Circle extends Styleable implements Drawable
|
|||||||
$this->radius = $radius;
|
$this->radius = $radius;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the SVG representation from this Drawable
|
||||||
|
*
|
||||||
|
* @param RenderContext $ctx The context to use for rendering
|
||||||
|
* @return DOMElement The SVG Element
|
||||||
|
*/
|
||||||
public function toSvg(RenderContext $ctx)
|
public function toSvg(RenderContext $ctx)
|
||||||
{
|
{
|
||||||
$coords = $ctx->toAbsolute($this->x, $this->y);
|
$coords = $ctx->toAbsolute($this->x, $this->y);
|
||||||
@ -58,5 +84,4 @@ class Circle extends Styleable implements Drawable
|
|||||||
$circle->setAttribute('style', $this->getStyle());
|
$circle->setAttribute('style', $this->getStyle());
|
||||||
return $circle;
|
return $circle;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -26,11 +26,21 @@
|
|||||||
*/
|
*/
|
||||||
// {{{ICINGA_LICENSE_HEADER}}}
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
|
||||||
|
|
||||||
namespace Icinga\Chart\Primitive;
|
namespace Icinga\Chart\Primitive;
|
||||||
|
|
||||||
use Icinga\Chart\Render\RenderContext;
|
use Icinga\Chart\Render\RenderContext;
|
||||||
|
|
||||||
interface Drawable {
|
/**
|
||||||
|
* Drawable element for creating svg out of components
|
||||||
function toSvg(RenderContext $ctx);
|
*/
|
||||||
|
interface Drawable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create the SVG representation from this Drawable
|
||||||
|
*
|
||||||
|
* @param RenderContext $ctx The context to use for rendering
|
||||||
|
*
|
||||||
|
* @return DOMElement The SVG Element
|
||||||
|
*/
|
||||||
|
public function toSvg(RenderContext $ctx);
|
||||||
}
|
}
|
@ -26,21 +26,55 @@
|
|||||||
*/
|
*/
|
||||||
// {{{ICINGA_LICENSE_HEADER}}}
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
|
||||||
|
|
||||||
namespace Icinga\Chart\Primitive;
|
namespace Icinga\Chart\Primitive;
|
||||||
|
|
||||||
|
|
||||||
use Icinga\Chart\Render\RenderContext;
|
use Icinga\Chart\Render\RenderContext;
|
||||||
|
|
||||||
class Line extends Styleable implements Drawable {
|
/**
|
||||||
|
* Drawable for the svg line element
|
||||||
|
*/
|
||||||
|
class Line extends Styleable implements Drawable
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default stroke width
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
public $strokeWidth = 1;
|
public $strokeWidth = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The line's start x coordinate
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
private $xStart = 0;
|
private $xStart = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The line's end x coordinate
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
private $xEnd = 0;
|
private $xEnd = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The line's start y coordinate
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
private $yStart = 0;
|
private $yStart = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The line's end y coordinate
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
private $yEnd = 0;
|
private $yEnd = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a line object starting at the first coordinate and ending at the second one
|
||||||
|
*
|
||||||
|
* @param int $x1 The line's start x coordinate
|
||||||
|
* @param int $y1 The line's start y coordinate
|
||||||
|
* @param int $x2 The line's end x coordinate
|
||||||
|
* @param int $y2 The line's end y coordinate
|
||||||
|
*/
|
||||||
public function __construct($x1, $y1, $x2, $y2)
|
public function __construct($x1, $y1, $x2, $y2)
|
||||||
{
|
{
|
||||||
$this->xStart = $x1;
|
$this->xStart = $x1;
|
||||||
@ -49,6 +83,12 @@ class Line extends Styleable implements Drawable {
|
|||||||
$this->yEnd = $y2;
|
$this->yEnd = $y2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the SVG representation from this Drawable
|
||||||
|
*
|
||||||
|
* @param RenderContext $ctx The context to use for rendering
|
||||||
|
* @return DOMElement The SVG Element
|
||||||
|
*/
|
||||||
public function toSvg(RenderContext $ctx)
|
public function toSvg(RenderContext $ctx)
|
||||||
{
|
{
|
||||||
$doc = $ctx->getDocument();
|
$doc = $ctx->getDocument();
|
||||||
|
@ -26,57 +26,151 @@
|
|||||||
*/
|
*/
|
||||||
// {{{ICINGA_LICENSE_HEADER}}}
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
|
||||||
|
|
||||||
namespace Icinga\Chart\Primitive;
|
namespace Icinga\Chart\Primitive;
|
||||||
|
|
||||||
|
|
||||||
use Icinga\Chart\Render\RenderContext;
|
use Icinga\Chart\Render\RenderContext;
|
||||||
|
|
||||||
class Path extends Styleable implements Drawable {
|
/**
|
||||||
public $strokeWidth = 1;
|
* Drawable for creating a svg path element
|
||||||
|
*/
|
||||||
protected $points = array();
|
class Path extends Styleable implements Drawable
|
||||||
protected $smooth = false;
|
{
|
||||||
|
|
||||||
const TPL_MOVE = "M %s %s ";
|
const TPL_MOVE = "M %s %s ";
|
||||||
const TPL_BEZIER = "S %s %s ";
|
const TPL_BEZIER = "S %s %s ";
|
||||||
const TPL_STRAIGHT = "L %s %s ";
|
const TPL_STRAIGHT = "L %s %s ";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default stroke width
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $strokeWidth = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True to treat coordinates as absolute values
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $isAbsolute = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The points to draw, in the order they are drawn
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $points = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True to draw the path discrete, i.e. make hard steps between points
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $discrete = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the path using the given points
|
||||||
|
*
|
||||||
|
* @param array $points Either a single [x, y] point or an array of x, y points
|
||||||
|
*/
|
||||||
public function __construct(array $points)
|
public function __construct(array $points)
|
||||||
{
|
{
|
||||||
$this->append($points);
|
$this->append($points);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append a single point or an array of points to this path
|
||||||
|
*
|
||||||
|
* @param array $points Either a single [x, y] point or an array of x, y points
|
||||||
|
*
|
||||||
|
* @return self Fluid interface
|
||||||
|
*/
|
||||||
public function append(array $points)
|
public function append(array $points)
|
||||||
{
|
{
|
||||||
$this->points += $points;
|
if (count($points) === 0) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
if (!is_array($points[0])) {
|
||||||
|
$points = array($points);
|
||||||
|
}
|
||||||
|
$this->points = array_merge($this->points, $points);
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setSmooth($bool)
|
/**
|
||||||
|
* Prepend a single point or an array of points to this path
|
||||||
|
*
|
||||||
|
* @param array $points Either a single [x, y] point or an array of x, y points
|
||||||
|
*
|
||||||
|
* @return self Fluid interface
|
||||||
|
*/
|
||||||
|
public function prepend(array $points)
|
||||||
{
|
{
|
||||||
$this->smooth = $bool;
|
if (count($points) === 0) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
if (!is_array($points[0])) {
|
||||||
|
$points = array($points);
|
||||||
|
}
|
||||||
|
$this->points = array_merge($points, $this->points);
|
||||||
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set this path to be discrete
|
||||||
|
*
|
||||||
|
* @param boolean $bool True to draw discrete or false to draw straight lines between points
|
||||||
|
*
|
||||||
|
* @return self Fluid interface
|
||||||
|
*/
|
||||||
|
public function setDiscrete($bool)
|
||||||
|
{
|
||||||
|
$this->discrete = $bool;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function toSvg(RenderContext $ctx) {
|
/**
|
||||||
|
* Mark this path as containing absolute coordinates
|
||||||
|
*
|
||||||
|
* @return self Fluid interface
|
||||||
|
*/
|
||||||
|
public function toAbsolute()
|
||||||
|
{
|
||||||
|
$this->isAbsolute = true;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the SVG representation from this Drawable
|
||||||
|
*
|
||||||
|
* @param RenderContext $ctx The context to use for rendering
|
||||||
|
* @return DOMElement The SVG Element
|
||||||
|
*/
|
||||||
|
public function toSvg(RenderContext $ctx)
|
||||||
|
{
|
||||||
$doc = $ctx->getDocument();
|
$doc = $ctx->getDocument();
|
||||||
$group = $doc->createElement('g');
|
$group = $doc->createElement('g');
|
||||||
|
|
||||||
$pathDescription = '';
|
$pathDescription = '';
|
||||||
$tpl = self::TPL_MOVE;
|
$tpl = self::TPL_MOVE;
|
||||||
|
$lastPoint = null;
|
||||||
foreach ($this->points as $point) {
|
foreach ($this->points as $point) {
|
||||||
$coords = $ctx->toAbsolute($point[0], $point[1]);
|
if (!$this->isAbsolute) {
|
||||||
$pathDescription .= vsprintf($tpl, $coords);
|
$point = $ctx->toAbsolute($point[0], $point[1]);
|
||||||
|
}
|
||||||
|
if ($lastPoint && $this->discrete) {
|
||||||
|
$pathDescription .= sprintf($tpl, $point[0], $lastPoint[1]);
|
||||||
|
}
|
||||||
|
$pathDescription .= vsprintf($tpl, $point);
|
||||||
|
$lastPoint = $point;
|
||||||
$tpl = self::TPL_STRAIGHT;
|
$tpl = self::TPL_STRAIGHT;
|
||||||
}
|
}
|
||||||
|
|
||||||
$path = $doc->createElement('path');
|
$path = $doc->createElement('path');
|
||||||
|
if ($this->id) {
|
||||||
|
$path->setAttribute('id', $this->id);
|
||||||
|
}
|
||||||
$path->setAttribute('d', $pathDescription);
|
$path->setAttribute('d', $pathDescription);
|
||||||
$path->setAttribute('style', $this->getStyle());
|
$path->setAttribute('style', $this->getStyle());
|
||||||
$group->appendChild($path);
|
$group->appendChild($path);
|
||||||
return $group;
|
return $group;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
299
library/Icinga/Chart/Primitive/PieSlice.php
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
<?php
|
||||||
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
/**
|
||||||
|
* This file is part of Icinga 2 Web.
|
||||||
|
*
|
||||||
|
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||||
|
* Copyright (C) 2013 Icinga Development Team
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*
|
||||||
|
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||||
|
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||||
|
* @author Icinga Development Team <info@icinga.org>
|
||||||
|
*/
|
||||||
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
|
||||||
|
namespace Icinga\Chart\Primitive;
|
||||||
|
|
||||||
|
use DOMElement;
|
||||||
|
use Icinga\Chart\Render\RenderContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component for drawing a pie slice
|
||||||
|
*/
|
||||||
|
class PieSlice extends Animatable implements Drawable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The radius of this pieslice relative to the canvas
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $radius = 50;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The start radian of the pie slice
|
||||||
|
* @var float
|
||||||
|
*/
|
||||||
|
private $startRadian = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The end radian of the pie slice
|
||||||
|
* @var float
|
||||||
|
*/
|
||||||
|
private $endRadian= 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The x position of the pie slice's center
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $x;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The y position of the pie slice's center
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $y;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The caption of the pie slice, empty string means no caption
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $caption = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The offset of the caption, shifting the indicator from the center of the pie slice
|
||||||
|
*
|
||||||
|
* This is required for nested pie slices
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $captionOffset = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The minimum radius the label must respect
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $outerCaptionBound = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An optional group element to add labels to when rendering
|
||||||
|
* @var DOMElement
|
||||||
|
*/
|
||||||
|
private $labelGroup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a pie slice
|
||||||
|
*
|
||||||
|
* @param int $radius The radius of the slice
|
||||||
|
* @param int $percent The percentage the slice represents
|
||||||
|
* @param int $percentStart The percentage where this slice starts
|
||||||
|
*/
|
||||||
|
public function __construct($radius, $percent, $percentStart = 0)
|
||||||
|
{
|
||||||
|
$this->x = $this->y = $this->radius = $radius;
|
||||||
|
|
||||||
|
$this->startRadian = M_PI * $percentStart/50;
|
||||||
|
$this->endRadian = M_PI * ($percent + $percentStart)/50;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the path for the pie slice
|
||||||
|
*
|
||||||
|
* @param int $x The x position of the pie slice
|
||||||
|
* @param int $y The y position of the pie slice
|
||||||
|
* @param int $r The absolute radius of the pie slice
|
||||||
|
*
|
||||||
|
* @return string A SVG path string
|
||||||
|
*/
|
||||||
|
private function getPieSlicePath($x, $y, $r)
|
||||||
|
{
|
||||||
|
// start at the center of the pieslice
|
||||||
|
$pathString = 'M ' . $x . ' ' . $y . ' ';
|
||||||
|
|
||||||
|
// The coordinate system is mirrored on the Y axis, so we have to flip cos and sin
|
||||||
|
$xStart = $x + intval($r * sin($this->startRadian));
|
||||||
|
$yStart = $y - intval($r * cos($this->startRadian));
|
||||||
|
$xEnd = $x + intval($r * sin($this->endRadian));
|
||||||
|
$yEnd = $y - intval($r * cos($this->endRadian));
|
||||||
|
|
||||||
|
// Draw a straight line to the upper part of the arc
|
||||||
|
$pathString .= 'L ' . $xStart . ' ' . $yStart;
|
||||||
|
// Instead of directly connecting the upper part of the arc (leaving a triangle), draw a bow with the radius
|
||||||
|
$pathString .= ' A ' . $r . ' ' . $r ;
|
||||||
|
// These are the flags for the bow, see the SVG path documentation for details
|
||||||
|
// http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands
|
||||||
|
$pathString .= ' 0 ' . (($this->endRadian - $this->startRadian > M_PI) ? '1' : '0 ') . ' 1';
|
||||||
|
|
||||||
|
// xEnd and yEnd are the lower point of the arc
|
||||||
|
$pathString .= ' '.$xEnd . ' ' . $yEnd;
|
||||||
|
return $pathString;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw the label handler and the text for this pie slice
|
||||||
|
*
|
||||||
|
* @param RenderContext $ctx The rendering context to use for coordinate translation
|
||||||
|
* @param int $r The radius of the pie in absolute coordinates
|
||||||
|
*
|
||||||
|
* @return DOMElement The group DOMElement containing the handle and label
|
||||||
|
*/
|
||||||
|
private function drawDescriptionLabel(RenderContext $ctx, $r)
|
||||||
|
{
|
||||||
|
$group = $ctx->getDocument()->createElement('g');
|
||||||
|
$rOuter = ($ctx->xToAbsolute($this->outerCaptionBound) + $ctx->yToAbsolute($this->outerCaptionBound)) / 2;
|
||||||
|
$addOffset = $rOuter - $r ;
|
||||||
|
if ($addOffset < 0) {
|
||||||
|
$addOffset = 0;
|
||||||
|
}
|
||||||
|
list($x, $y) = $ctx->toAbsolute($this->x, $this->y);
|
||||||
|
$midRadius = $this->startRadian + ($this->endRadian - $this->startRadian) / 2;
|
||||||
|
list($offsetX, $offsetY) = $ctx->toAbsolute($this->captionOffset, $this->captionOffset);
|
||||||
|
|
||||||
|
$midX = $x + intval(($offsetX + $r)/2 * sin($midRadius));
|
||||||
|
$midY = $y - intval(($offsetY + $r)/2 * cos($midRadius));
|
||||||
|
|
||||||
|
// Draw the handle
|
||||||
|
$path = new Path(array($midX, $midY));
|
||||||
|
|
||||||
|
$midX += ($addOffset + $r/1.8) * ($midRadius > M_PI ? -1 : 1);
|
||||||
|
$path->append(array($midX, $midY))->toAbsolute();
|
||||||
|
|
||||||
|
$midX += intval($r/2 * sin(M_PI/7)) * ($midRadius > M_PI ? -1 : 1);
|
||||||
|
$midY -= intval($r/2 * cos(M_PI/7)) * ($midRadius < M_PI*1.5 && $midRadius > M_PI/2 ? -1 : 1);
|
||||||
|
|
||||||
|
if ($ctx->ytoRelative($midY) > 100) {
|
||||||
|
$midY = $ctx->yToAbsolute(100);
|
||||||
|
} elseif ($ctx->ytoRelative($midY) < 0) {
|
||||||
|
$midY = $ctx->yToAbsolute(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
$path->append(array($midX , $midY));
|
||||||
|
$rel = $ctx->toRelative($midX, $midY);
|
||||||
|
|
||||||
|
// Draw the text box
|
||||||
|
$text = new Text($rel[0]+1.5, $rel[1], $this->caption);
|
||||||
|
$text->setFontSize('2.5em');
|
||||||
|
$text->setAlignment(($midRadius > M_PI ? Text::ALIGN_END : Text::ALIGN_START));
|
||||||
|
|
||||||
|
$group->appendChild($path->toSvg($ctx));
|
||||||
|
$group->appendChild($text->toSvg($ctx));
|
||||||
|
|
||||||
|
return $group;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the x position of the pie slice
|
||||||
|
*
|
||||||
|
* @param int $x The new x position
|
||||||
|
*
|
||||||
|
* @return self Fluid interface
|
||||||
|
*/
|
||||||
|
public function setX($x)
|
||||||
|
{
|
||||||
|
$this->x = $x;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the y position of the pie slice
|
||||||
|
*
|
||||||
|
* @param int $y The new y position
|
||||||
|
*
|
||||||
|
* @return self Fluid interface
|
||||||
|
*/
|
||||||
|
public function setY($y)
|
||||||
|
{
|
||||||
|
$this->y = $y;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a root element to be used for drawing labels
|
||||||
|
*
|
||||||
|
* @param DOMElement $group The label group
|
||||||
|
*
|
||||||
|
* @return self Fluid interface
|
||||||
|
*/
|
||||||
|
public function setLabelGroup(DOMElement $group)
|
||||||
|
{
|
||||||
|
$this->labelGroup = $group;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the caption for this label
|
||||||
|
*
|
||||||
|
* @param string $caption The caption for this element
|
||||||
|
*
|
||||||
|
* @return self Fluid interface
|
||||||
|
*/
|
||||||
|
public function setCaption($caption)
|
||||||
|
{
|
||||||
|
$this->caption = $caption;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the internal offset of the caption handle
|
||||||
|
*
|
||||||
|
* @param int $offset The offset for the caption handle
|
||||||
|
*
|
||||||
|
* @return self Fluid interface
|
||||||
|
*/
|
||||||
|
public function setCaptionOffset($offset)
|
||||||
|
{
|
||||||
|
$this->captionOffset = $offset;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the minimum radius to be used for drawing labels
|
||||||
|
*
|
||||||
|
* @param int $bound The offset for the caption text
|
||||||
|
*
|
||||||
|
* @return self Fluid interface
|
||||||
|
*/
|
||||||
|
public function setOuterCaptionBound($bound)
|
||||||
|
{
|
||||||
|
$this->outerCaptionBound = $bound;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the SVG representation from this Drawable
|
||||||
|
*
|
||||||
|
* @param RenderContext $ctx The context to use for rendering
|
||||||
|
*
|
||||||
|
* @return DOMElement The SVG Element
|
||||||
|
*/
|
||||||
|
public function toSvg(RenderContext $ctx)
|
||||||
|
{
|
||||||
|
$doc = $ctx->getDocument();
|
||||||
|
$group = $doc->createElement('g');
|
||||||
|
$r = ($ctx->xToAbsolute($this->radius) + $ctx->yToAbsolute($this->radius)) / 2;
|
||||||
|
list($x, $y) = $ctx->toAbsolute($this->x, $this->y);
|
||||||
|
|
||||||
|
$slicePath = $doc->createElement('path');
|
||||||
|
|
||||||
|
$slicePath->setAttribute('d', $this->getPieSlicePath($x, $y, $r));
|
||||||
|
$slicePath->setAttribute('style', $this->getStyle());
|
||||||
|
|
||||||
|
$group->appendChild($slicePath);
|
||||||
|
if ($this->caption != "") {
|
||||||
|
$lblGroup = ($this->labelGroup ? $this->labelGroup : $group);
|
||||||
|
$lblGroup->appendChild($this->drawDescriptionLabel($ctx, $r));
|
||||||
|
}
|
||||||
|
return $group;
|
||||||
|
}
|
||||||
|
}
|
68
library/Icinga/Chart/Primitive/RawElement.php
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
/**
|
||||||
|
* This file is part of Icinga 2 Web.
|
||||||
|
*
|
||||||
|
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||||
|
* Copyright (C) 2013 Icinga Development Team
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*
|
||||||
|
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||||
|
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||||
|
* @author Icinga Development Team <info@icinga.org>
|
||||||
|
*/
|
||||||
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
|
||||||
|
namespace Icinga\Chart\Primitive;
|
||||||
|
|
||||||
|
use DOMElement;
|
||||||
|
use Icinga\Chart\Render\RenderContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for raw elements to be added as Drawable's
|
||||||
|
*/
|
||||||
|
class RawElement implements Drawable
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The DOMElement wrapped by this Drawable
|
||||||
|
*
|
||||||
|
* @var DOMElement
|
||||||
|
*/
|
||||||
|
private $domEl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create this RawElement
|
||||||
|
*
|
||||||
|
* @param DOMElement $el The element to wrap here
|
||||||
|
*/
|
||||||
|
public function __construct(DOMElement $el)
|
||||||
|
{
|
||||||
|
$this->domEl = $el;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the SVG representation from this Drawable
|
||||||
|
*
|
||||||
|
* @param RenderContext $ctx The context to use for rendering
|
||||||
|
*
|
||||||
|
* @return DOMElement The SVG Element
|
||||||
|
*/
|
||||||
|
public function toSvg(RenderContext $ctx)
|
||||||
|
{
|
||||||
|
return $this->domEl;
|
||||||
|
}
|
||||||
|
}
|
@ -26,20 +26,53 @@
|
|||||||
*/
|
*/
|
||||||
// {{{ICINGA_LICENSE_HEADER}}}
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
|
||||||
|
|
||||||
namespace Icinga\Chart\Primitive;
|
namespace Icinga\Chart\Primitive;
|
||||||
|
|
||||||
use Icinga\Chart\Render\RenderContext;
|
use Icinga\Chart\Render\RenderContext;
|
||||||
|
|
||||||
class Rect extends Styleable implements Drawable
|
/**
|
||||||
|
* Drawable representing the SVG rect element
|
||||||
|
*/
|
||||||
|
class Rect extends Animatable implements Drawable
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* The x position
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
private $x;
|
private $x;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The y position
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
private $y;
|
private $y;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The width of this rect
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
private $width;
|
private $width;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The height of this rect
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
private $height;
|
private $height;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to keep the ratio
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $keepRatio = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create this rect
|
||||||
|
*
|
||||||
|
* @param int $x The x position of the rect
|
||||||
|
* @param int $y The y position of the rectangle
|
||||||
|
* @param int $width The width of the rectangle
|
||||||
|
* @param int $height The height of the rectangle
|
||||||
|
*/
|
||||||
public function __construct($x, $y, $width, $height)
|
public function __construct($x, $y, $width, $height)
|
||||||
{
|
{
|
||||||
$this->x = $x;
|
$this->x = $x;
|
||||||
@ -48,19 +81,42 @@ class Rect extends Styleable implements Drawable
|
|||||||
$this->height = $height;
|
$this->height = $height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call to let the rectangle keep the ratio
|
||||||
|
*/
|
||||||
|
public function keepRatio()
|
||||||
|
{
|
||||||
|
$this->keepRatio = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the SVG representation from this Drawable
|
||||||
|
*
|
||||||
|
* @param RenderContext $ctx The context to use for rendering
|
||||||
|
*
|
||||||
|
* @return DOMElement The SVG Element
|
||||||
|
*/
|
||||||
public function toSvg(RenderContext $ctx)
|
public function toSvg(RenderContext $ctx)
|
||||||
{
|
{
|
||||||
$doc = $ctx->getDocument();
|
$doc = $ctx->getDocument();
|
||||||
$rect = $doc->createElement('rect');
|
$rect = $doc->createElement('rect');
|
||||||
|
|
||||||
list($x, $y) = $ctx->toAbsolute($this->x, $this->y);
|
list($x, $y) = $ctx->toAbsolute($this->x, $this->y);
|
||||||
|
if ($this->keepRatio) {
|
||||||
|
$ctx->keepRatio();
|
||||||
|
}
|
||||||
list($width, $height) = $ctx->toAbsolute($this->width, $this->height);
|
list($width, $height) = $ctx->toAbsolute($this->width, $this->height);
|
||||||
|
if ($this->keepRatio) {
|
||||||
|
$ctx->ignoreRatio();
|
||||||
|
}
|
||||||
$rect->setAttribute('x', $x);
|
$rect->setAttribute('x', $x);
|
||||||
$rect->setAttribute('y', $y);
|
$rect->setAttribute('y', $y);
|
||||||
$rect->setAttribute('width', $width);
|
$rect->setAttribute('width', $width);
|
||||||
$rect->setAttribute('height', $height);
|
$rect->setAttribute('height', $height);
|
||||||
$rect->setAttribute('style', $this->getStyle());
|
$rect->setAttribute('style', $this->getStyle());
|
||||||
|
|
||||||
|
$this->appendAnimation($rect, $ctx);
|
||||||
|
|
||||||
return $rect;
|
return $rect;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -29,18 +29,48 @@
|
|||||||
|
|
||||||
namespace Icinga\Chart\Primitive;
|
namespace Icinga\Chart\Primitive;
|
||||||
|
|
||||||
|
/**
|
||||||
class Styleable {
|
* Base class for stylable drawables
|
||||||
|
*/
|
||||||
public $strokeWidth = 0;
|
class Styleable
|
||||||
public $strokeColor = '#000';
|
{
|
||||||
public $fill = 'none';
|
|
||||||
public $additionalStyle = '';
|
|
||||||
public $opacity = '1';
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param mixed $stroke
|
* The stroke width to use
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $strokeWidth = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The stroke color to use
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $strokeColor = '#000';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The fill color to use
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $fill = 'none';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Additional styles to be appended to the style attribute
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $additionalStyle = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id of this element
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $id = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the stroke width for this drawable
|
||||||
|
*
|
||||||
|
* @param string $stroke The stroke with with unit
|
||||||
|
*
|
||||||
|
* @return self Fluid interface
|
||||||
*/
|
*/
|
||||||
public function setStrokeWidth($width)
|
public function setStrokeWidth($width)
|
||||||
{
|
{
|
||||||
@ -48,30 +78,68 @@ class Styleable {
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the color for the stroke or none for no stroke
|
||||||
|
*
|
||||||
|
* @param string $color The color to set for the stroke
|
||||||
|
*
|
||||||
|
* @return self Fluid interface
|
||||||
|
*/
|
||||||
public function setStrokeColor($color)
|
public function setStrokeColor($color)
|
||||||
{
|
{
|
||||||
$this->strokeColor = $color ? $color : 'none';
|
$this->strokeColor = $color ? $color : 'none';
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set additional styles for this drawable
|
||||||
|
*
|
||||||
|
* @param string $styles The styles to set additionally
|
||||||
|
*
|
||||||
|
* @return self Fluid interface
|
||||||
|
*/
|
||||||
public function setAdditionalStyle($styles)
|
public function setAdditionalStyle($styles)
|
||||||
{
|
{
|
||||||
$this->additionalStyle = $styles;
|
$this->additionalStyle = $styles;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setFill($color = null) {
|
/**
|
||||||
|
* Set the fill for this styleable
|
||||||
|
*
|
||||||
|
* @param string $color The color to use for filling or null to use no fill
|
||||||
|
*
|
||||||
|
* @return self Fluid interface
|
||||||
|
*/
|
||||||
|
public function setFill($color = null)
|
||||||
|
{
|
||||||
$this->fill = $color ? $color : 'none';
|
$this->fill = $color ? $color : 'none';
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the id for this element
|
||||||
|
*
|
||||||
|
* @param string $id The id to set for this element
|
||||||
|
*
|
||||||
|
* @return self Fluid interface
|
||||||
|
*/
|
||||||
|
public function setId($id)
|
||||||
|
{
|
||||||
|
$this->id = $id;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the content of the style attribute as a string
|
||||||
|
*
|
||||||
|
* @return string T
|
||||||
|
*/
|
||||||
public function getStyle()
|
public function getStyle()
|
||||||
{
|
{
|
||||||
$base = sprintf("fill: %s; stroke: %s;stroke-width: %s;", $this->fill, $this->strokeColor, $this->strokeWidth);
|
$base = sprintf("fill: %s; stroke: %s;stroke-width: %s;", $this->fill, $this->strokeColor, $this->strokeWidth);
|
||||||
$base .= ';' . $this->additionalStyle . ';';
|
$base .= ';' . $this->additionalStyle . ';';
|
||||||
return $base;
|
return $base;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -33,55 +33,137 @@ namespace Icinga\Chart\Primitive;
|
|||||||
use Icinga\Chart\Render\RenderContext;
|
use Icinga\Chart\Render\RenderContext;
|
||||||
use DOMText;
|
use DOMText;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for the SVG text element
|
||||||
|
*/
|
||||||
class Text extends Styleable implements Drawable
|
class Text extends Styleable implements Drawable
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Align the text to end at the x and y position
|
||||||
|
*/
|
||||||
const ALIGN_END = 'end';
|
const ALIGN_END = 'end';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Align the text to start at the x and y position
|
||||||
|
*/
|
||||||
const ALIGN_START = 'start';
|
const ALIGN_START = 'start';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Align the text to be centered at the x and y position
|
||||||
|
*/
|
||||||
const ALIGN_MIDDLE = 'middle';
|
const ALIGN_MIDDLE = 'middle';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normal left to right orientation
|
||||||
|
*/
|
||||||
const ORIENTATION_HORIZONTAL = "";
|
const ORIENTATION_HORIZONTAL = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Top down orientation (rotated by 90°)
|
||||||
|
*/
|
||||||
const ORIENTATION_VERTICAL = "writing-mode: tb;";
|
const ORIENTATION_VERTICAL = "writing-mode: tb;";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The x position of the Text
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
private $x;
|
private $x;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The y position of the Text
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
private $y;
|
private $y;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The text content
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
private $text;
|
private $text;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The size of the font
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
private $fontSize = '1.5em';
|
private $fontSize = '1.5em';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default fill color
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
public $fill = '#000';
|
public $fill = '#000';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The alignment of the text
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
private $alignment = self::ALIGN_START;
|
private $alignment = self::ALIGN_START;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new text drawable
|
||||||
|
*
|
||||||
|
* @param int $x The x position of the text
|
||||||
|
* @param int $y The y position of the text
|
||||||
|
* @param string $text The text this component should contain
|
||||||
|
* @param string $fontSize The font size of the text
|
||||||
|
*/
|
||||||
public function __construct($x, $y, $text, $fontSize = '1.5em')
|
public function __construct($x, $y, $text, $fontSize = '1.5em')
|
||||||
{
|
{
|
||||||
$this->x = $x;
|
$this->x = $x;
|
||||||
$this->y = $y;
|
$this->y = $y;
|
||||||
$this->text = $text;
|
$this->text = $text;
|
||||||
|
$this->fontSize = $fontSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the font size of the svg text element
|
||||||
|
*
|
||||||
|
* @param string $size The font size including a unit
|
||||||
|
*
|
||||||
|
* @return self Fluid interface
|
||||||
|
*/
|
||||||
public function setFontSize($size)
|
public function setFontSize($size)
|
||||||
{
|
{
|
||||||
$this->fontSize = $size;
|
$this->fontSize = $size;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the the text alignment with one of the ALIGN_* constants
|
||||||
|
*
|
||||||
|
* @param String $align
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
public function setAlignment($align)
|
public function setAlignment($align)
|
||||||
{
|
{
|
||||||
$this->alignment = $align;
|
$this->alignment = $align;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the SVG representation from this Drawable
|
||||||
|
*
|
||||||
|
* @param RenderContext $ctx The context to use for rendering
|
||||||
|
*
|
||||||
|
* @return DOMElement The SVG Element
|
||||||
|
*/
|
||||||
public function toSvg(RenderContext $ctx)
|
public function toSvg(RenderContext $ctx)
|
||||||
{
|
{
|
||||||
list($x, $y) = $ctx->toAbsolute($this->x, $this->y);
|
list($x, $y) = $ctx->toAbsolute($this->x, $this->y);
|
||||||
$text = $ctx->getDocument()->createElement('text');
|
$text = $ctx->getDocument()->createElement('text');
|
||||||
|
|
||||||
$text->setAttribute('x', $x - 15);
|
$text->setAttribute('x', $x - 15);
|
||||||
$text->setAttribute('style', $this->getStyle() . ';font-size:' . $this->fontSize . '; font-family: Verdana, serif;'
|
$text->setAttribute(
|
||||||
|
'style',
|
||||||
|
$this->getStyle()
|
||||||
|
. ';font-size:' . $this->fontSize
|
||||||
|
. '; font-family: Verdana, serif;'
|
||||||
. 'font-weight: normal; font-style: normal;'
|
. 'font-weight: normal; font-style: normal;'
|
||||||
. 'text-anchor: ' . $this->alignment);
|
. 'text-anchor: ' . $this->alignment
|
||||||
|
);
|
||||||
$text->setAttribute('y', $y);
|
$text->setAttribute('y', $y);
|
||||||
$text->appendChild(new DOMText($this->text));
|
$text->appendChild(new DOMText($this->text));
|
||||||
|
|
||||||
return $text;
|
return $text;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -26,20 +26,78 @@
|
|||||||
*/
|
*/
|
||||||
// {{{ICINGA_LICENSE_HEADER}}}
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
|
||||||
|
|
||||||
namespace Icinga\Chart\Render;
|
namespace Icinga\Chart\Render;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Layout class encapsulating size, padding and margin information
|
||||||
|
*/
|
||||||
class LayoutBox
|
class LayoutBox
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Padding index for top padding
|
||||||
|
*/
|
||||||
|
const PADDING_TOP = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Padding index for right padding
|
||||||
|
*/
|
||||||
|
const PADDING_RIGHT = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Padding index for bottom padding
|
||||||
|
*/
|
||||||
|
const PADDING_BOTTOM = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Padding index for left padding
|
||||||
|
*/
|
||||||
|
const PADDING_LEFT = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The height of this layout element
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
private $height;
|
private $height;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The width of this layout element
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
private $width;
|
private $width;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The x position of this layout
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
private $x;
|
private $x;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The y position of this layout
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
private $y;
|
private $y;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The padding of this layout
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
private $padding = array(0, 0, 0, 0);
|
private $padding = array(0, 0, 0, 0);
|
||||||
private $margin = array(0,0,0,0);
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create this layout box
|
||||||
|
*
|
||||||
|
* Note that x, y, width and height are relative: x with 0 means leftmost, x with 100 means rightmost
|
||||||
|
*
|
||||||
|
* @param int $x The relative x coordinate
|
||||||
|
* @param int $y The relative y coordinate
|
||||||
|
* @param int $width The optional, relative width
|
||||||
|
* @param int $height The optional, relative height
|
||||||
|
*/
|
||||||
public function __construct($x, $y, $width = null, $height = null)
|
public function __construct($x, $y, $width = null, $height = null)
|
||||||
{
|
{
|
||||||
$this->height = $height ? $height : 100;
|
$this->height = $height ? $height : 100;
|
||||||
@ -48,35 +106,42 @@ class LayoutBox
|
|||||||
$this->y = $y;
|
$this->y = $y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a padding to all four sides uniformly
|
||||||
|
*
|
||||||
|
* @param int $padding The padding to set for all four sides
|
||||||
|
*/
|
||||||
public function setUniformPadding($padding)
|
public function setUniformPadding($padding)
|
||||||
{
|
{
|
||||||
$this->padding = array($padding, $padding, $padding, $padding);
|
$this->padding = array($padding, $padding, $padding, $padding);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the padding for this LayoutBox
|
||||||
|
*
|
||||||
|
* @param int $top The top side padding
|
||||||
|
* @param int $right The right side padding
|
||||||
|
* @param int $bottom The bottom side padding
|
||||||
|
* @param int $left The left side padding
|
||||||
|
*/
|
||||||
public function setPadding($top, $right, $bottom, $left)
|
public function setPadding($top, $right, $bottom, $left)
|
||||||
{
|
{
|
||||||
$this->padding = array($top, $right, $bottom, $left);
|
$this->padding = array($top, $right, $bottom, $left);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setUniformMargin($margin)
|
/**
|
||||||
{
|
* Return a string containing the SVG transform attribute values for the padding
|
||||||
$this->margin = array($margin, $margin, $margin, $margin);
|
*
|
||||||
}
|
* @param RenderContext $ctx The context to determine the translation coordinates
|
||||||
|
*
|
||||||
public function setMargin($top, $right, $bottom, $left)
|
* @return string The transformation string
|
||||||
{
|
*/
|
||||||
$this->margin = array($top, $right, $bottom, $left);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getOuterTranslate(RenderContext $ctx)
|
|
||||||
{
|
|
||||||
list($marginX, $marginY) = $ctx->toAbsolute($this->margin[3], $this->margin[0]);
|
|
||||||
return sprintf('translate(%s, %s)', $marginX, $marginY);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getInnerTransform(RenderContext $ctx)
|
public function getInnerTransform(RenderContext $ctx)
|
||||||
{
|
{
|
||||||
list($translateX, $translateY) = $ctx->toAbsolute($this->padding[3] + $this->getX(), $this->padding[0] + $this->getY());
|
list($translateX, $translateY) = $ctx->toAbsolute(
|
||||||
|
$this->padding[self::PADDING_LEFT] + $this->getX(),
|
||||||
|
$this->padding[self::PADDING_TOP] + $this->getY()
|
||||||
|
);
|
||||||
list($scaleX, $scaleY) = $ctx->paddingToScaleFactor($this->padding);
|
list($scaleX, $scaleY) = $ctx->paddingToScaleFactor($this->padding);
|
||||||
|
|
||||||
$scaleX *= $this->getWidth()/100;
|
$scaleX *= $this->getWidth()/100;
|
||||||
@ -84,21 +149,26 @@ class LayoutBox
|
|||||||
return sprintf('translate(%s, %s) scale(%s, %s)', $translateX, $translateY, $scaleX, $scaleY);
|
return sprintf('translate(%s, %s) scale(%s, %s)', $translateX, $translateY, $scaleX, $scaleY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String representation for this Layout, for debug purposes
|
||||||
|
*
|
||||||
|
* @return string A string containing the bounds of this LayoutBox
|
||||||
|
*/
|
||||||
public function __toString()
|
public function __toString()
|
||||||
{
|
{
|
||||||
return sprintf("Rectangle: x: %s y: %s, height: %s, width: %s\n", $this->x, $this->y, $this->height, $this->width);
|
return sprintf(
|
||||||
|
'Rectangle: x: %s y: %s, height: %s, width: %s',
|
||||||
|
$this->x,
|
||||||
|
$this->y,
|
||||||
|
$this->height,
|
||||||
|
$this->width
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array
|
* Return a four element array with the padding
|
||||||
*/
|
*
|
||||||
public function getMargin()
|
* @return array The padding of this LayoutBox
|
||||||
{
|
|
||||||
return $this->margin;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array
|
|
||||||
*/
|
*/
|
||||||
public function getPadding()
|
public function getPadding()
|
||||||
{
|
{
|
||||||
@ -106,40 +176,42 @@ class LayoutBox
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return \Icinga\Util\Dimension
|
* Return the height of this LayoutBox
|
||||||
|
*
|
||||||
|
* @return int The height of this box
|
||||||
*/
|
*/
|
||||||
public function getHeight()
|
public function getHeight()
|
||||||
{
|
{
|
||||||
return $this->height+($this->margin[0] + $this->margin[2]);
|
return $this->height;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return \Icinga\Util\Dimension
|
* Return the width of this LayoutBox
|
||||||
|
*
|
||||||
|
* @return int The width of this box
|
||||||
*/
|
*/
|
||||||
public function getWidth()
|
public function getWidth()
|
||||||
{
|
{
|
||||||
return $this->width-($this->margin[1] + $this->margin[3]);
|
return $this->width;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return \Icinga\Util\Dimension
|
* Return the x position of this LayoutBox
|
||||||
|
*
|
||||||
|
* @return int The x position of this box
|
||||||
*/
|
*/
|
||||||
public function getX()
|
public function getX()
|
||||||
{
|
{
|
||||||
return $this->x + $this->margin[1];
|
return $this->x;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return \Icinga\Util\Dimension
|
* Return the y position of this LayoutBox
|
||||||
|
*
|
||||||
|
* @return int The y position of this box
|
||||||
*/
|
*/
|
||||||
public function getY()
|
public function getY()
|
||||||
{
|
{
|
||||||
return $this->y + $this->margin[0];
|
return $this->y;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRatio()
|
|
||||||
{
|
|
||||||
return $this->width->getValue()/$this->height->getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
@ -32,31 +32,90 @@ namespace Icinga\Chart\Render;
|
|||||||
use Icinga\Util\Dimension;
|
use Icinga\Util\Dimension;
|
||||||
use DOMDocument;
|
use DOMDocument;
|
||||||
|
|
||||||
class RenderContext {
|
/**
|
||||||
|
* Context for rendering, handles ratio based coordinate calculations.
|
||||||
|
*
|
||||||
|
* The most important functions when rendering are the toAbsolute and roRelative
|
||||||
|
* values, taking world coordinates and translating them into local coordinates.
|
||||||
|
*/
|
||||||
|
class RenderContext
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The base size of the viewport, i.e. how many units are available on a 1:1 ratio
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
private $viewBoxSize = array(1000, 1000);
|
private $viewBoxSize = array(1000, 1000);
|
||||||
private $padding = array(0, 0);
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The DOMDocument for modifying the elements
|
||||||
|
*
|
||||||
|
* @var DOMDocument
|
||||||
|
*/
|
||||||
private $document;
|
private $document;
|
||||||
private $ratio;
|
|
||||||
|
/**
|
||||||
|
* If true no ratio correction will be made
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
private $respectRatio = false;
|
private $respectRatio = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current padding (scale) factor on the X axis
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
private $paddingFacX = 1;
|
private $paddingFacX = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current padding (scale) factor on the Y Axis
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
private $paddingFacY = 1;
|
private $paddingFacY = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ratio on the x side. A x ration of 2 means that the width of the SVG is divided in 2000
|
||||||
|
* units (see $viewBox)
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $xratio = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ratio on the y side. A y ration of 2 means that the height of the SVG is divided in 2000
|
||||||
|
* units (see $viewBox)
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $yratio = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new context for the given DOM Document
|
||||||
|
*
|
||||||
|
* @param DOMDocument $document The DOM document represented by this context
|
||||||
|
* @param int $width The width (may be approximate) of the document
|
||||||
|
* (only required for ratio calculation)
|
||||||
|
* @param int $height The height (may be approximate) of the document
|
||||||
|
* (only required for ratio calculation)
|
||||||
|
*/
|
||||||
public function __construct(DOMDocument $document, $width, $height)
|
public function __construct(DOMDocument $document, $width, $height)
|
||||||
{
|
{
|
||||||
$this->document = $document;
|
$this->document = $document;
|
||||||
$this->ratio = $width/$height;
|
if ($width > $height) {
|
||||||
|
$this->xratio = $width/$height;
|
||||||
|
} elseif ($height < $width) {
|
||||||
|
$this->yratio = $width/$height;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setViewBoxSize($x, $y)
|
|
||||||
{
|
|
||||||
$this->viewBoxSize = array($x, $y);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return mixed
|
* Return the document represented by this Rendering context
|
||||||
|
*
|
||||||
|
* @return DOMDocument The DOMDocument for creating files
|
||||||
*/
|
*/
|
||||||
public function getDocument()
|
public function getDocument()
|
||||||
{
|
{
|
||||||
@ -64,56 +123,143 @@ class RenderContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return mixed
|
* Let successive toAbsolute operations ignore ratio correction.
|
||||||
|
*
|
||||||
|
* This can be called to avoid distortion on certain elements like rectangles
|
||||||
*/
|
*/
|
||||||
public function getNrOfUnitsX()
|
|
||||||
{
|
|
||||||
return intval($this->viewBoxSize[0] * $this->ratio);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setPadding($horizontal, $vertical) {
|
|
||||||
$this->padding = array($horizontal, $vertical);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPadding()
|
|
||||||
{
|
|
||||||
return array($this->paddingFacX, $this->paddingFacY);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function keepRatio()
|
public function keepRatio()
|
||||||
{
|
{
|
||||||
$this->respectRatio = true;
|
$this->respectRatio = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Let successive toAbsolute operations perform ratio correction
|
||||||
|
*
|
||||||
|
* This will cause distortion on certain elements like rectangles
|
||||||
|
*/
|
||||||
public function ignoreRatio()
|
public function ignoreRatio()
|
||||||
{
|
{
|
||||||
$this->respectRatio = false;
|
$this->respectRatio = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return mixed
|
* Return how many unit s are available in the Y axis
|
||||||
|
*
|
||||||
|
* @return int The number of units available on the y axis
|
||||||
*/
|
*/
|
||||||
public function getNrOfUnitsY()
|
public function getNrOfUnitsY()
|
||||||
{
|
{
|
||||||
return $this->viewBoxSize[1];
|
return intval($this->viewBoxSize[1] * $this->yratio);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return how many unit s are available in the X axis
|
||||||
|
*
|
||||||
|
* @return int The number of units available on the x axis
|
||||||
|
*/
|
||||||
|
public function getNrOfUnitsX()
|
||||||
|
{
|
||||||
|
return intval($this->viewBoxSize[0] * $this->xratio);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms the x,y coordinate from relative coordinates to absolute world coordinates
|
||||||
|
*
|
||||||
|
* (50, 50) would be a point in the middle of the document and map to 500, 1000 on a
|
||||||
|
* 1000 x 1000 viewbox with a 1:2 ratio.
|
||||||
|
*
|
||||||
|
* @param int $x The relative x coordinate
|
||||||
|
* @param int $y The relative y coordinate
|
||||||
|
*
|
||||||
|
* @return array An x,y tupel containing absolute coordinates
|
||||||
|
* @see RenderContext::toRelative
|
||||||
|
*/
|
||||||
public function toAbsolute($x, $y)
|
public function toAbsolute($x, $y)
|
||||||
{
|
{
|
||||||
return array(
|
return array($this->xToAbsolute($x), $this->yToAbsolute($y));
|
||||||
$this->getNrOfUnitsX() / 100 * $x / ($this->respectRatio ? $this->ratio : 1),// * $this->paddingFacX,
|
|
||||||
$this->getNrOfUnitsY() / 100 * $y// * $this->paddingFacY
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms the x,y coordinate from absolute coordinates to relative world coordinates
|
||||||
|
*
|
||||||
|
* This is the inverse function of toAbsolute
|
||||||
|
*
|
||||||
|
* @param int $x The absolute x coordinate
|
||||||
|
* @param int $y The absolute y coordinate
|
||||||
|
*
|
||||||
|
* @return array An x,y tupel containing absolute coordinates
|
||||||
|
* @see RenderContext::toAbsolute
|
||||||
|
*/
|
||||||
|
public function toRelative($x, $y)
|
||||||
|
{
|
||||||
|
return array($this->xToRelative($x), $this->yToRelative($y));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the scale transformation required to apply the padding on an Canvas
|
||||||
|
*
|
||||||
|
* @param array $padding A 4 element array containing top, right, bottom and left padding
|
||||||
|
*
|
||||||
|
* @return array An array containing the x and y scale
|
||||||
|
*/
|
||||||
|
public function paddingToScaleFactor(array $padding)
|
||||||
|
{
|
||||||
|
list($horizontalPadding, $verticalPadding) = $this->toAbsolute(
|
||||||
|
$padding[LayoutBox::PADDING_RIGHT] + $padding[LayoutBox::PADDING_LEFT],
|
||||||
|
$padding[LayoutBox::PADDING_TOP] + $padding[LayoutBox::PADDING_BOTTOM]
|
||||||
|
);
|
||||||
|
|
||||||
public function paddingToScaleFactor(array $padding) {
|
|
||||||
list($horizontalPadding, $verticalPadding) = $this->toAbsolute($padding[1] + $padding[3], $padding[0] + $padding[2]);
|
|
||||||
return array(
|
return array(
|
||||||
($this->getNrOfUnitsX() - $horizontalPadding) / $this->getNrOfUnitsX(),
|
($this->getNrOfUnitsX() - $horizontalPadding) / $this->getNrOfUnitsX(),
|
||||||
($this->getNrOfUnitsY() - $verticalPadding) / $this->getNrOfUnitsY()
|
($this->getNrOfUnitsY() - $verticalPadding) / $this->getNrOfUnitsY()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform a relative x coordinate to an absolute one
|
||||||
|
*
|
||||||
|
* @param int $x A relative x coordinate
|
||||||
|
*
|
||||||
|
* @return int $y An absolute x coordinate
|
||||||
|
**/
|
||||||
|
public function xToAbsolute($x)
|
||||||
|
{
|
||||||
|
return $this->getNrOfUnitsX() / 100 * $x / ($this->respectRatio ? $this->xratio : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform a relative y coordinate to an absolute one
|
||||||
|
*
|
||||||
|
* @param int $y A relative y coordinate
|
||||||
|
*
|
||||||
|
* @return int $y An absolute y coordinate
|
||||||
|
*/
|
||||||
|
public function yToAbsolute($y)
|
||||||
|
{
|
||||||
|
return $this->getNrOfUnitsY() / 100 * $y / ($this->respectRatio ? $this->yratio : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform a absolute x coordinate to an relative one
|
||||||
|
*
|
||||||
|
* @param int $x An absolute x coordinate
|
||||||
|
*
|
||||||
|
* @return int $x A relative x coordinate
|
||||||
|
*/
|
||||||
|
public function xToRelative($x)
|
||||||
|
{
|
||||||
|
return $x / $this->getNrOfUnitsX() * 100 * ($this->respectRatio ? $this->xratio : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform a absolute y coordinate to an relative one
|
||||||
|
*
|
||||||
|
* @param int $y An absolute x coordinate
|
||||||
|
*
|
||||||
|
* @return int $y A relative x coordinate
|
||||||
|
*/
|
||||||
|
public function yToRelative($y)
|
||||||
|
{
|
||||||
|
return $y / $this->getNrOfUnitsY() * 100 * ($this->respectRatio ? $this->yratio : 1);
|
||||||
|
}
|
||||||
}
|
}
|
@ -29,6 +29,7 @@
|
|||||||
namespace Icinga\Chart;
|
namespace Icinga\Chart;
|
||||||
|
|
||||||
use DOMNode;
|
use DOMNode;
|
||||||
|
use DOMElement;
|
||||||
use DOMDocument;
|
use DOMDocument;
|
||||||
use DOMImplementation;
|
use DOMImplementation;
|
||||||
use Exception;
|
use Exception;
|
||||||
@ -37,6 +38,12 @@ use Icinga\Chart\Render\LayoutBox;
|
|||||||
use Icinga\Chart\Render\RenderContext;
|
use Icinga\Chart\Render\RenderContext;
|
||||||
use Icinga\Chart\Primitive\Canvas;
|
use Icinga\Chart\Primitive\Canvas;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SVG Renderer component.
|
||||||
|
*
|
||||||
|
* Creates the basic DOM tree of the SVG to use
|
||||||
|
*
|
||||||
|
*/
|
||||||
class SVGRenderer
|
class SVGRenderer
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@ -56,22 +63,28 @@ class SVGRenderer
|
|||||||
/**
|
/**
|
||||||
* The root layer for all elements
|
* The root layer for all elements
|
||||||
*
|
*
|
||||||
* @var DOMNode
|
* @var Canvas
|
||||||
*/
|
*/
|
||||||
private $rootCanvas;
|
private $rootCanvas;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The position and dimension of each layer
|
* The width of this renderer
|
||||||
*
|
*
|
||||||
* @var array
|
* @var int
|
||||||
*/
|
*/
|
||||||
private $layerInfo = array();
|
|
||||||
|
|
||||||
private $width = 100;
|
private $width = 100;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The height of this renderer
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
private $height = 100;
|
private $height = 100;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the root document and the SVG root node
|
||||||
|
*/
|
||||||
private function createRootDocument()
|
private function createRootDocument()
|
||||||
{
|
{
|
||||||
$implementation = new DOMImplementation();
|
$implementation = new DOMImplementation();
|
||||||
@ -87,11 +100,12 @@ class SVGRenderer
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function setRootCanvas(Canvas $root) {
|
|
||||||
$this->rootCanvas = $root;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the outer SVG box containing the root svg element and namespace and return it
|
||||||
|
*
|
||||||
|
* @return DOMElement The SVG root node
|
||||||
|
*/
|
||||||
private function createOuterBox()
|
private function createOuterBox()
|
||||||
{
|
{
|
||||||
$ctx = $this->createRenderContext();
|
$ctx = $this->createRenderContext();
|
||||||
@ -103,23 +117,29 @@ class SVGRenderer
|
|||||||
$svg->setAttribute(
|
$svg->setAttribute(
|
||||||
'viewBox',
|
'viewBox',
|
||||||
sprintf(
|
sprintf(
|
||||||
'0 0 %s %s', $ctx->getNrOfUnitsX(), $ctx->getNrOfUnitsY()
|
'0 0 %s %s',
|
||||||
|
$ctx->getNrOfUnitsX(),
|
||||||
|
$ctx->getNrOfUnitsY()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
return $svg;
|
return $svg;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialises the XML-document, SVG-element and this figure's root layer
|
* 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) {
|
public function __construct($width, $height)
|
||||||
|
{
|
||||||
$this->width = $width;
|
$this->width = $width;
|
||||||
$this->height = $height;
|
$this->height = $height;
|
||||||
$this->setRootCanvas(new Canvas('root', new LayoutBox(0,0)));
|
$this->rootCanvas = new Canvas('root', new LayoutBox(0, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the XML-document
|
* Render the SVG-document
|
||||||
*
|
*
|
||||||
* @return string The resulting XML structure
|
* @return string The resulting XML structure
|
||||||
*/
|
*/
|
||||||
@ -132,44 +152,23 @@ class SVGRenderer
|
|||||||
return $this->document->saveXML();
|
return $this->document->saveXML();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a render context that will be used for rendering elements
|
||||||
|
*
|
||||||
|
* @return RenderContext The created RenderContext instance
|
||||||
|
*/
|
||||||
public function createRenderContext()
|
public function createRenderContext()
|
||||||
{
|
{
|
||||||
return new RenderContext($this->document, $this->width, $this->height);
|
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()
|
public function getCanvas()
|
||||||
{
|
{
|
||||||
return $this->rootCanvas;
|
return $this->rootCanvas;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Draw a line
|
|
||||||
*
|
|
||||||
* TODO: Arguments
|
|
||||||
*/
|
|
||||||
public function line()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draw a pie slice
|
|
||||||
*
|
|
||||||
* TODO: Arguments
|
|
||||||
*/
|
|
||||||
public function slice()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draw a bar
|
|
||||||
*
|
|
||||||
* TODO: Arguments
|
|
||||||
*/
|
|
||||||
public function bar()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -26,12 +26,34 @@
|
|||||||
*/
|
*/
|
||||||
// {{{ICINGA_LICENSE_HEADER}}}
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
|
||||||
|
|
||||||
namespace Icinga\Chart\Unit;
|
namespace Icinga\Chart\Unit;
|
||||||
|
|
||||||
use Iterator;
|
use Iterator;
|
||||||
|
|
||||||
interface AxisUnit extends Iterator {
|
/**
|
||||||
|
* Base class for Axis Units
|
||||||
|
*
|
||||||
|
* Concrete subclasses must implement the iterator interface, with
|
||||||
|
* getCurrent returning the axis relative position and getValue the label
|
||||||
|
* that will be displayed
|
||||||
|
*/
|
||||||
|
interface AxisUnit extends Iterator
|
||||||
|
{
|
||||||
|
|
||||||
function addValues(array $dataset, $id=0);
|
/**
|
||||||
function transform($value);
|
* Add a dataset to this AxisUnit, required for dynamic min and max vlaues
|
||||||
|
*
|
||||||
|
* @param array $dataset The dataset that will be shown in the Axis
|
||||||
|
* @param int $id The idx in the dataset (0 for x, 1 for y)
|
||||||
|
*/
|
||||||
|
public function addValues(array $dataset, $id = 0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform the given absolute value in an axis relative value
|
||||||
|
*
|
||||||
|
* @param int $value The absolute, dataset dependend value
|
||||||
|
*
|
||||||
|
* @return int An axis relative value
|
||||||
|
*/
|
||||||
|
public function transform($value);
|
||||||
}
|
}
|
195
library/Icinga/Chart/Unit/CalendarUnit.php
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
<?php
|
||||||
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
/**
|
||||||
|
* This file is part of Icinga 2 Web.
|
||||||
|
*
|
||||||
|
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||||
|
* Copyright (C) 2013 Icinga Development Team
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*
|
||||||
|
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||||
|
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||||
|
* @author Icinga Development Team <info@icinga.org>
|
||||||
|
*/
|
||||||
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
|
||||||
|
|
||||||
|
namespace Icinga\Chart\Unit;
|
||||||
|
|
||||||
|
use Icinga\Application\Icinga;
|
||||||
|
use Zend_Config;
|
||||||
|
use Icinga\Application\Config as IcingaConfig;
|
||||||
|
use Icinga\Util\DateTimeFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calendar Axis Unit that transforms timestamps into user-readable values
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class CalendarUnit extends LinearUnit
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constant for a minute
|
||||||
|
*/
|
||||||
|
const MINUTE = 60;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constant for an hour
|
||||||
|
*/
|
||||||
|
const HOUR = 3600;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constant for a day
|
||||||
|
*/
|
||||||
|
const DAY = 864000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constant for ~a month
|
||||||
|
* 30 Days, this is sufficient for our needs
|
||||||
|
*/
|
||||||
|
const MONTH = 2592000; // x
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array containing all labels that will be displayed
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $labels = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The date format to use
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $dateFormat = 'd-m';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time format to use
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $timeFormat = 'g:i:s';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the labels for the given dataset
|
||||||
|
*/
|
||||||
|
private function createLabels()
|
||||||
|
{
|
||||||
|
$this->labels = array();
|
||||||
|
$duration = $this->getMax() - $this->getMin();
|
||||||
|
|
||||||
|
if ($duration <= self::HOUR) {
|
||||||
|
$unit = self::MINUTE;
|
||||||
|
} elseif ($duration <= self::DAY) {
|
||||||
|
$unit = self::HOUR;
|
||||||
|
} elseif ($duration <= self::MONTH) {
|
||||||
|
$unit = self::DAY;
|
||||||
|
} else {
|
||||||
|
$unit = self::MONTH;
|
||||||
|
}
|
||||||
|
$this->calculateLabels($unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the labels for this dataset
|
||||||
|
*
|
||||||
|
* @param integer $unit The unit to use as the basis for calculation
|
||||||
|
*/
|
||||||
|
private function calculateLabels($unit)
|
||||||
|
{
|
||||||
|
$fac = DateTimeFactory::create();
|
||||||
|
|
||||||
|
$duration = $this->getMax() - $this->getMin();
|
||||||
|
|
||||||
|
// Calculate number of ticks, but not more than 30
|
||||||
|
$tickCount = ($duration/$unit * 10);
|
||||||
|
if ($tickCount > 30) {
|
||||||
|
$tickCount = 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
$step = $duration / $tickCount;
|
||||||
|
$format = $this->timeFormat;
|
||||||
|
if ($unit === self::DAY) {
|
||||||
|
$format = $this->dateFormat;
|
||||||
|
} elseif ($unit === self::MONTH) {
|
||||||
|
$format = $this->dateFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
for ($i = 0; $i <= $duration; $i += $step) {
|
||||||
|
$this->labels[] = $fac->setTimestamp($this->getMin() + $i)->format($format);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a dataset to this CalendarUnit and update labels
|
||||||
|
*
|
||||||
|
* @param array $dataset The dataset to update
|
||||||
|
* @param int $idx The index to use for determining the data
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function addValues(array $dataset, $idx = 0)
|
||||||
|
{
|
||||||
|
parent::addValues($dataset, $idx);
|
||||||
|
$this->createLabels();
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the current axis relative position
|
||||||
|
*
|
||||||
|
* @return int The position of the next tick (between 0 and 100)
|
||||||
|
*/
|
||||||
|
public function current()
|
||||||
|
{
|
||||||
|
return 100 * (key($this->labels) / count($this->labels));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move to next tick
|
||||||
|
*/
|
||||||
|
public function next()
|
||||||
|
{
|
||||||
|
next($this->labels);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the current tick caption
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function key()
|
||||||
|
{
|
||||||
|
return current($this->labels);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true when the iterator is in a valid range
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function valid()
|
||||||
|
{
|
||||||
|
return current($this->labels) !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rewind the internal array
|
||||||
|
*/
|
||||||
|
public function rewind()
|
||||||
|
{
|
||||||
|
reset($this->labels);
|
||||||
|
}
|
||||||
|
}
|
@ -26,36 +26,92 @@
|
|||||||
*/
|
*/
|
||||||
// {{{ICINGA_LICENSE_HEADER}}}
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
|
||||||
|
|
||||||
namespace Icinga\Chart\Unit;
|
namespace Icinga\Chart\Unit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Linear tick distribution over the axis
|
||||||
|
*/
|
||||||
|
class LinearUnit implements AxisUnit
|
||||||
|
{
|
||||||
|
|
||||||
class LinearAxis implements AxisUnit {
|
/**
|
||||||
|
* The minimum value to display
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $min;
|
||||||
|
|
||||||
private $min;
|
/**
|
||||||
private $max;
|
* The maximum value to display
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $max;
|
||||||
|
|
||||||
private $staticMin = false;
|
/**
|
||||||
private $staticMax = false;
|
* True when the minimum value is static and isn't affected by the dataset
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $staticMin = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True when the maximum value is static and isn't affected by the dataset
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $staticMax = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of ticks to use
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
private $nrOfTicks = 10;
|
private $nrOfTicks = 10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The currently displayed tick
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
private $currentTick = 0;
|
private $currentTick = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The currently displayed value
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
private $currentValue = 0;
|
private $currentValue = 0;
|
||||||
|
|
||||||
public function __construct($nrOfTicks = 10) {
|
/**
|
||||||
|
* Create and initialize this AxisUnit
|
||||||
|
*
|
||||||
|
* @param int $nrOfTicks The number of ticks to use
|
||||||
|
*/
|
||||||
|
public function __construct($nrOfTicks = 10)
|
||||||
|
{
|
||||||
$this->min = PHP_INT_MAX;
|
$this->min = PHP_INT_MAX;
|
||||||
$this->max = ~PHP_INT_MAX;
|
$this->max = ~PHP_INT_MAX;
|
||||||
$this->nrOfTicks = $nrOfTicks;
|
$this->nrOfTicks = $nrOfTicks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a dataset and calculate the minimum and maximum value for this AxisUnit
|
||||||
|
*
|
||||||
|
* @param array $dataset The dataset to add
|
||||||
|
* @param int $idx The idx (0 for x, 1 for y)
|
||||||
|
*
|
||||||
|
* @return self Fluent interface
|
||||||
|
*/
|
||||||
public function addValues(array $dataset, $idx = 0)
|
public function addValues(array $dataset, $idx = 0)
|
||||||
{
|
{
|
||||||
|
|
||||||
$datapoints = array();
|
$datapoints = array();
|
||||||
|
|
||||||
foreach ($dataset['data'] as $points) {
|
foreach ($dataset['data'] as $points) {
|
||||||
$datapoints[] = $points[$idx];
|
$datapoints[] = $points[$idx];
|
||||||
}
|
}
|
||||||
|
if (empty($datapoints)) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
sort($datapoints);
|
sort($datapoints);
|
||||||
if (!$this->staticMax) {
|
if (!$this->staticMax) {
|
||||||
$this->max = max($this->max, $datapoints[count($datapoints)-1]);
|
$this->max = max($this->max, $datapoints[count($datapoints)-1]);
|
||||||
@ -69,6 +125,12 @@ class LinearAxis implements AxisUnit {
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform the absolute value to an axis relative value
|
||||||
|
*
|
||||||
|
* @param int $value The absolute coordinate from the dataset
|
||||||
|
* @return float|int The axis relative coordinate (between 0 and 100)
|
||||||
|
*/
|
||||||
public function transform($value)
|
public function transform($value)
|
||||||
{
|
{
|
||||||
if ($value < $this->min) {
|
if ($value < $this->min) {
|
||||||
@ -80,30 +142,48 @@ class LinearAxis implements AxisUnit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the position of the current tick
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
public function current()
|
public function current()
|
||||||
{
|
{
|
||||||
return $this->currentTick;
|
return $this->currentTick;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the next tick and tick value
|
||||||
|
*/
|
||||||
public function next()
|
public function next()
|
||||||
{
|
{
|
||||||
$this->currentTick += (100 / $this->nrOfTicks);
|
$this->currentTick += (100 / $this->nrOfTicks);
|
||||||
$this->currentValue += (($this->max - $this->min) / $this->nrOfTicks);
|
$this->currentValue += (($this->max - $this->min) / $this->nrOfTicks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the label for the current tick
|
||||||
|
*
|
||||||
|
* @return string The label for the current tick
|
||||||
|
*/
|
||||||
public function key()
|
public function key()
|
||||||
{
|
{
|
||||||
return $this->currentValue;
|
return (string) $this->currentValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True when we're at a valid tick (iterator interface)
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
public function valid()
|
public function valid()
|
||||||
{
|
{
|
||||||
return $this->currentTick >= 0 && $this->currentTick <= 100;
|
return $this->currentTick >= 0 && $this->currentTick <= 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the current tick and label value
|
||||||
|
*/
|
||||||
public function rewind()
|
public function rewind()
|
||||||
{
|
{
|
||||||
$this->currentTick = 0;
|
$this->currentTick = 0;
|
||||||
@ -111,7 +191,9 @@ class LinearAxis implements AxisUnit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $max
|
* Set the axis maximum value to a fixed value
|
||||||
|
*
|
||||||
|
* @param int $max The new maximum value
|
||||||
*/
|
*/
|
||||||
public function setMax($max)
|
public function setMax($max)
|
||||||
{
|
{
|
||||||
@ -122,7 +204,9 @@ class LinearAxis implements AxisUnit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $min
|
* Set the axis minimum value to a fixed value
|
||||||
|
*
|
||||||
|
* @param int $min The new minimum value
|
||||||
*/
|
*/
|
||||||
public function setMin($min)
|
public function setMin($min)
|
||||||
{
|
{
|
||||||
@ -132,4 +216,23 @@ class LinearAxis implements AxisUnit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the current minimum value of the axis
|
||||||
|
*
|
||||||
|
* @return int The minimum set for this axis
|
||||||
|
*/
|
||||||
|
public function getMin()
|
||||||
|
{
|
||||||
|
return $this->min;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the current maximum value of the axis
|
||||||
|
*
|
||||||
|
* @return int The maximum set for this axis
|
||||||
|
*/
|
||||||
|
public function getMax()
|
||||||
|
{
|
||||||
|
return $this->max;
|
||||||
|
}
|
||||||
}
|
}
|