summaryrefslogtreecommitdiffstats
path: root/library/Icinga/Web/Widget/Chart
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 12:39:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 12:39:39 +0000
commit8ca6cc32b2c789a3149861159ad258f2cb9491e3 (patch)
tree2492de6f1528dd44eaa169a5c1555026d9cb75ec /library/Icinga/Web/Widget/Chart
parentInitial commit. (diff)
downloadicingaweb2-8ca6cc32b2c789a3149861159ad258f2cb9491e3.tar.xz
icingaweb2-8ca6cc32b2c789a3149861159ad258f2cb9491e3.zip
Adding upstream version 2.11.4.upstream/2.11.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'library/Icinga/Web/Widget/Chart')
-rw-r--r--library/Icinga/Web/Widget/Chart/HistoryColorGrid.php373
-rw-r--r--library/Icinga/Web/Widget/Chart/InlinePie.php257
2 files changed, 630 insertions, 0 deletions
diff --git a/library/Icinga/Web/Widget/Chart/HistoryColorGrid.php b/library/Icinga/Web/Widget/Chart/HistoryColorGrid.php
new file mode 100644
index 0000000..ed1e377
--- /dev/null
+++ b/library/Icinga/Web/Widget/Chart/HistoryColorGrid.php
@@ -0,0 +1,373 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Widget\Chart;
+
+use DateInterval;
+use DateTime;
+use Icinga\Util\Color;
+use Icinga\Web\Widget\AbstractWidget;
+
+/**
+ * Display a colored grid that visualizes a set of values for each day
+ * on a given time-frame.
+ */
+class HistoryColorGrid extends AbstractWidget
+{
+ const CAL_GROW_INTO_PAST = 'past';
+ const CAL_GROW_INTO_PRESENT = 'present';
+
+ const ORIENTATION_VERTICAL = 'vertical';
+ const ORIENTATION_HORIZONTAL = 'horizontal';
+
+ public $weekFlow = self::CAL_GROW_INTO_PAST;
+ public $orientation = self::ORIENTATION_VERTICAL;
+ public $weekStartMonday = true;
+
+ private $maxValue = 1;
+
+ private $start = null;
+ private $end = null;
+ private $data = array();
+ private $color;
+ public $opacity = 1.0;
+
+ public function __construct($color = '#51e551', $start = null, $end = null)
+ {
+ $this->setColor($color);
+ if (isset($start)) {
+ $this->start = $this->tsToDateStr($start);
+ }
+ if (isset($end)) {
+ $this->end = $this->tsToDateStr($end);
+ }
+ }
+
+ /**
+ * Set the displayed data-set
+ *
+ * @param $events array The history events to display as an array of arrays:
+ * value: The value to display
+ * caption: The caption on mouse-over
+ * url: The url to open on click.
+ */
+ public function setData(array $events)
+ {
+ $this->data = $events;
+ $start = time();
+ $end = time();
+ foreach ($this->data as $entry) {
+ $entry['value'] = intval($entry['value']);
+ }
+ foreach ($this->data as $date => $entry) {
+ $time = strtotime($date);
+ if ($entry['value'] > $this->maxValue) {
+ $this->maxValue = $entry['value'];
+ }
+ if ($time > $end) {
+ $end = $time;
+ }
+ if ($time < $start) {
+ $start = $time;
+ }
+ }
+ if (!isset($this->start)) {
+ $this->start = $this->tsToDateStr($start);
+ }
+ if (!isset($this->end)) {
+ $this->end = $this->tsToDateStr($end);
+ }
+ }
+
+ /**
+ * Set the used color.
+ *
+ * @param $color
+ */
+ public function setColor($color)
+ {
+ $this->color = $color;
+ }
+
+ /**
+ * Set the used opacity
+ *
+ * @param $opacity
+ */
+ public function setOpacity($opacity)
+ {
+ $this->opacity = $opacity;
+ }
+
+ /**
+ * Calculate the color to display for the given value.
+ *
+ * @param $value integer
+ *
+ * @return string The color-string to use for this entry.
+ */
+ private function calculateColor($value)
+ {
+ $saturation = $value / $this->maxValue;
+ return Color::changeSaturation($this->color, $saturation);
+ }
+
+ /**
+ * Render the html to display the given $day
+ *
+ * @param $day string The day to display YYYY-MM-DD
+ *
+ * @return string The rendered html
+ */
+ private function renderDay($day)
+ {
+ if (array_key_exists($day, $this->data) && $this->data[$day]['value'] > 0) {
+ $entry = $this->data[$day];
+ return '<a ' .
+ 'style="background-color: ' . $this->calculateColor($entry['value']) . ';'
+ . ' opacity: ' . $this->opacity . ';" ' .
+ 'aria-label="' . $entry['caption'] . '" ' .
+ 'title="' . $entry['caption'] . '" ' .
+ 'href="' . $entry['url'] . '" ' .
+ '></a>';
+ } else {
+ return '<span ' .
+ 'style="background-color: ' . $this->calculateColor(0) . '; opacity: ' . $this->opacity . ';" ' .
+ 'title="No entries for ' . $day . '" ' .
+ '></span>';
+ }
+ }
+
+ /**
+ * Render the grid with an horizontal alignment.
+ *
+ * @param array $grid The values returned from the createGrid function
+ *
+ * @return string The rendered html
+ */
+ private function renderHorizontal($grid)
+ {
+ $weeks = $grid['weeks'];
+ $months = $grid['months'];
+ $years = $grid['years'];
+ $html = '<table class="historycolorgrid">';
+ $html .= '<tr><th></th>';
+ $old = -1;
+ foreach ($months as $week => $month) {
+ if ($old !== $month) {
+ $old = $month;
+ $txt = $this->monthName($month, $years[$week]);
+ } else {
+ $txt = '';
+ }
+ $html .= '<th>' . $txt . '</th>';
+ }
+ $html .= '</tr>';
+ for ($i = 0; $i < 7; $i++) {
+ $html .= $this->renderWeekdayHorizontal($i, $weeks);
+ }
+ $html .= '</table>';
+ return $html;
+ }
+
+ /**
+ * @param $grid
+ *
+ * @return string
+ */
+ private function renderVertical($grid)
+ {
+ $years = $grid['years'];
+ $weeks = $grid['weeks'];
+ $months = $grid['months'];
+ $html = '<table class="historycolorgrid">';
+ $html .= '<tr>';
+ for ($i = 0; $i < 7; $i++) {
+ $html .= '<th>' . $this->weekdayName($this->weekStartMonday ? $i + 1 : $i) . "</th>";
+ }
+ $html .= '</tr>';
+ $old = -1;
+ foreach ($weeks as $index => $week) {
+ for ($i = 0; $i < 7; $i++) {
+ if (array_key_exists($i, $week)) {
+ $html .= '<td>' . $this->renderDay($week[$i]) . '</td>';
+ } else {
+ $html .= '<td></td>';
+ }
+ }
+ if ($old !== $months[$index]) {
+ $old = $months[$index];
+ $txt = $this->monthName($old, $years[$index]);
+ } else {
+ $txt = '';
+ }
+ $html .= '<td class="weekday">' . $txt . '</td></tr>';
+ }
+ $html .= '</table>';
+ return $html;
+ }
+
+ /**
+ * Render the row for the given weekday.
+ *
+ * @param integer $weekday The day to render (0-6)
+ * @param array $weeks The weeks
+ *
+ * @return string The formatted table-row
+ */
+ private function renderWeekdayHorizontal($weekday, &$weeks)
+ {
+ $html = '<tr><td class="weekday">'
+ . $this->weekdayName($this->weekStartMonday ? $weekday + 1 : $weekday)
+ . '</td>';
+ foreach ($weeks as $week) {
+ if (array_key_exists($weekday, $week)) {
+ $html .= '<td>' . $this->renderDay($week[$weekday]) . '</td>';
+ } else {
+ $html .= '<td></td>';
+ }
+ }
+ $html .= '</tr>';
+ return $html;
+ }
+
+
+
+ /**
+ * @return array
+ */
+ private function createGrid()
+ {
+ $weeks = array(array());
+ $week = 0;
+ $months = array();
+ $years = array();
+ $start = strtotime($this->start);
+ $year = intval(date('Y', $start));
+ $month = intval(date('n', $start));
+ $day = intval(date('j', $start));
+ $weekday = intval(date('w', $start));
+ if ($this->weekStartMonday) {
+ // 0 => monday, 6 => sunday
+ $weekday = $weekday === 0 ? 6 : $weekday - 1;
+ }
+
+ $date = $this->toDateStr($day, $month, $year);
+ $weeks[0][$weekday] = $date;
+ $years[0] = $year;
+ $months[0] = $month;
+ while ($date !== $this->end) {
+ $day++;
+ $weekday++;
+ if ($weekday > 6) {
+ $weekday = 0;
+ $weeks[] = array();
+ // PRESENT => The last day of week determines the month
+ if ($this->weekFlow === self::CAL_GROW_INTO_PRESENT) {
+ $months[$week] = $month;
+ $years[$week] = $year;
+ }
+ $week++;
+ }
+ if ($day > date('t', mktime(0, 0, 0, $month, 1, $year))) {
+ $month++;
+ if ($month > 12) {
+ $year++;
+ $month = 1;
+ }
+ $day = 1;
+ }
+ if ($weekday === 0) {
+ // PAST => The first day of each week determines the month
+ if ($this->weekFlow === self::CAL_GROW_INTO_PAST) {
+ $months[$week] = $month;
+ $years[$week] = $year;
+ }
+ }
+ $date = $this->toDateStr($day, $month, $year);
+ $weeks[$week][$weekday] = $date;
+ };
+ $years[$week] = $year;
+ $months[$week] = $month;
+ if ($this->weekFlow == self::CAL_GROW_INTO_PAST) {
+ return array(
+ 'weeks' => array_reverse($weeks),
+ 'months' => array_reverse($months),
+ 'years' => array_reverse($years)
+ );
+ }
+ return array(
+ 'weeks' => $weeks,
+ 'months' => $months,
+ 'years' => $years
+ );
+ }
+
+ /**
+ * Get the localized month-name for the given month
+ *
+ * @param integer $month The month-number
+ *
+ * @return string The
+ */
+ private function monthName($month, $year)
+ {
+ // TODO: find a way to render years without messing up the layout
+ $dt = new DateTime($year . '-' . $month . '-01');
+ return $dt->format('M');
+ }
+
+ /**
+ * @param $weekday
+ *
+ * @return string
+ */
+ private function weekdayName($weekday)
+ {
+ $sun = new DateTime('last Sunday');
+ $interval = new DateInterval('P' . $weekday . 'D');
+ $sun->add($interval);
+ return substr($sun->format('D'), 0, 2);
+ }
+
+ /**
+ *
+ *
+ * @param $timestamp
+ *
+ * @return bool|string
+ */
+ private function tsToDateStr($timestamp)
+ {
+ return date('Y-m-d', $timestamp);
+ }
+
+ /**
+ * @param $day
+ * @param $mon
+ * @param $year
+ *
+ * @return string
+ */
+ private function toDateStr($day, $mon, $year)
+ {
+ $day = $day > 9 ? (string)$day : '0' . (string)$day;
+ $mon = $mon > 9 ? (string)$mon : '0' . (string)$mon;
+ return $year . '-' . $mon . '-' . $day;
+ }
+
+ /**
+ * @return string
+ */
+ public function render()
+ {
+ if (empty($this->data)) {
+ return '<div>No entries</div>';
+ }
+ $grid = $this->createGrid();
+ if ($this->orientation === self::ORIENTATION_HORIZONTAL) {
+ return $this->renderHorizontal($grid);
+ }
+ return $this->renderVertical($grid);
+ }
+}
diff --git a/library/Icinga/Web/Widget/Chart/InlinePie.php b/library/Icinga/Web/Widget/Chart/InlinePie.php
new file mode 100644
index 0000000..21b4ca4
--- /dev/null
+++ b/library/Icinga/Web/Widget/Chart/InlinePie.php
@@ -0,0 +1,257 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Widget\Chart;
+
+use Icinga\Chart\PieChart;
+use Icinga\Module\Monitoring\Plugin\PerfdataSet;
+use Icinga\Util\StringHelper;
+use Icinga\Web\Widget\AbstractWidget;
+use Icinga\Web\Url;
+use Icinga\Util\Format;
+use Icinga\Application\Logger;
+use Icinga\Exception\IcingaException;
+use stdClass;
+
+/**
+ * A SVG-PieChart intended to be displayed as a small icon next to labels, to offer a better visualization of the
+ * shown data
+ *
+ * NOTE: When InlinePies are shown in a dynamically loaded view, like the side-bar or in the dashboard, the SVGs will
+ * be replaced with a jQuery-Sparkline to save resources @see loader.js
+ *
+ * @package Icinga\Web\Widget\Chart
+ */
+class InlinePie extends AbstractWidget
+{
+ const NUMBER_FORMAT_NONE = 'none';
+ const NUMBER_FORMAT_TIME = 'time';
+ const NUMBER_FORMAT_BYTES = 'bytes';
+ const NUMBER_FORMAT_RATIO = 'ratio';
+
+ public static $colorsHostStates = array(
+ '#44bb77', // up
+ '#ff99aa', // down
+ '#cc77ff', // unreachable
+ '#77aaff' // pending
+ );
+
+ public static $colorsHostStatesHandledUnhandled = array(
+ '#44bb77', // up
+ '#44bb77',
+ '#ff99aa', // down
+ '#ff5566',
+ '#cc77ff', // unreachable
+ '#aa44ff',
+ '#77aaff', // pending
+ '#77aaff'
+ );
+
+ public static $colorsServiceStates = array(
+ '#44bb77', // Ok
+ '#ffaa44', // Warning
+ '#ff99aa', // Critical
+ '#aa44ff', // Unknown
+ '#77aaff' // Pending
+ );
+
+ public static $colorsServiceStatesHandleUnhandled = array(
+ '#44bb77', // Ok
+ '#44bb77',
+ '#ffaa44', // Warning
+ '#ffcc66',
+ '#ff99aa', // Critical
+ '#ff5566',
+ '#cc77ff', // Unknown
+ '#aa44ff',
+ '#77aaff', // Pending
+ '#77aaff'
+ );
+
+ /**
+ * The template string used for rendering this widget
+ *
+ * @var string
+ */
+ private $template = '<div class="inline-pie {class}">{svg}</div>';
+
+ /**
+ * The colors used to display the slices of this pie-chart.
+ *
+ * @var array
+ */
+ private $colors = array('#049BAF', '#ffaa44', '#ff5566', '#ddccdd');
+
+ /**
+ * The title of the chart
+ *
+ * @var string
+ */
+ private $title;
+
+ /**
+ * @var int
+ */
+ private $size = 16;
+
+ /**
+ * The data displayed by the pie-chart
+ *
+ * @var array
+ */
+ private $data;
+
+ /**
+ * @var
+ */
+ private $class = '';
+
+ /**
+ * Set the data to be displayed.
+ *
+ * @param $data array
+ *
+ * @return $this
+ */
+ public function setData(array $data)
+ {
+ $this->data = $data;
+
+ return $this;
+ }
+
+ /**
+ * Set the size of the inline pie
+ *
+ * @param int $size Sets both, the height and width
+ *
+ * @return $this
+ */
+ public function setSize($size = null)
+ {
+ $this->size = $size;
+
+ return $this;
+ }
+
+ /**
+ * Set the class to define the
+ *
+ * @param $class
+ *
+ * @return $this
+ */
+ public function setSparklineClass($class)
+ {
+ $this->class = $class;
+
+ return $this;
+ }
+
+ /**
+ * Set the colors used by the slices of the pie chart.
+ *
+ * @param array $colors
+ *
+ * @return $this
+ */
+ public function setColors(array $colors = null)
+ {
+ $this->colors = $colors;
+
+ return $this;
+ }
+
+ /**
+ * Set the title of the displayed Data
+ *
+ * @param string $title
+ *
+ * @return $this
+ */
+ public function setTitle($title)
+ {
+ $this->title = $this->view()->escape($title);
+
+ return $this;
+ }
+
+ /**
+ * Create a new InlinePie
+ *
+ * @param array $data The data displayed by the slices
+ * @param string $title The title of this Pie
+ * @param array $colors An array of RGB-Color values to use
+ */
+ public function __construct(array $data, $title, $colors = null)
+ {
+ $this->setTitle($title);
+
+ if (array_key_exists('data', $data)) {
+ $this->data = $data['data'];
+ if (array_key_exists('colors', $data)) {
+ $this->colors = $data['colors'];
+ }
+ } else {
+ $this->setData($data);
+ }
+
+ if (isset($colors)) {
+ $this->setColors($colors);
+ } else {
+ $this->setColors($this->colors);
+ }
+ }
+
+ /**
+ * Renders this widget via the given view and returns the
+ * HTML as a string
+ *
+ * @return string
+ */
+ public function render()
+ {
+ $pie = new PieChart();
+ $pie->alignTopLeft();
+ $pie->disableLegend();
+ $pie->drawPie([
+ 'data' => $this->data,
+ 'colors' => $this->colors
+ ]);
+
+ if ($this->view()->layout()->getLayout() === 'pdf') {
+ try {
+ $png = $pie->toPng($this->size, $this->size);
+ return '<img class="inlinepie" src="data:image/png;base64,' . base64_encode($png) . '" />';
+ } catch (IcingaException $_) {
+ return '';
+ }
+ }
+
+ $pie->title = $this->title;
+ $pie->description = $this->title;
+
+ $template = $this->template;
+ $template = str_replace('{class}', $this->class, $template);
+ $template = str_replace('{svg}', $pie->render(), $template);
+
+ return $template;
+ }
+
+ public static function createFromStateSummary(stdClass $states, $title, array $colors)
+ {
+ $handledUnhandledStates = [];
+ foreach ($states as $key => $value) {
+ if (StringHelper::endsWith($key, '_handled') || StringHelper::endsWith($key, '_unhandled')) {
+ $handledUnhandledStates[$key] = $value;
+ }
+ }
+
+ $chart = new self(array_values($handledUnhandledStates), $title, $colors);
+
+ return $chart
+ ->setSize(50)
+ ->setTitle('')
+ ->setSparklineClass('sparkline-multi');
+ }
+}